home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
TeX 1995 July
/
TeX CD-ROM July 1995 (Disc 1)(Walnut Creek)(1995).ISO
/
dviware
/
vutex
/
vutex.web
(
.txt
)
< prev
Wrap
Texinfo Document
|
1990-10-01
|
128KB
|
3,106 lines
% vutex:web
% This program was written by Warren Wolfe, and all rights are reserved.
% Copyright 1987 CUBE Software, Victoria, B.C., Canada.
% Version 1.00 ASCII terminal driver: December 1987
% 1.01 Modification for horizontal and vertical shifts: June 1989
% 1.02 Modification for page action: November 1989
% Here is TeX material that gets inserted after \input webmac
\def\hang{\hangindent 3em\indent\ignorespaces}
\font\ninerm=amr9
\let\mc=\ninerm % medium caps for names like PASCAL
\def\PASCAL{{\mc PASCAL}}
\def\vutex{{\bf vu\TeX}}
\def\tamu{Texas A\char38 M}
\def\(#1){} % this is used to make section names sort themselves better
\def\9#1{} % this is used for sort keys in the index
\def\title{{\tenrm \vutex}}
\def\contentspagenumber{0}
\def\topofcontents{\null
\def\titlepage{F} % include headline on the contents page
\def\rheader{\mainfont\hfil \contentspagenumber}
\vfill
\centerline{\titlefont The {\ttitlefont \vutex} processor}
\vskip 15pt
\centerline{(Version 1.02, Dec 1989)}
\vfill}
\def\botofcontents{
\bigskip
\centerline{This report was written by Warren Wolfe.}
\centerline{based on the original by Tomas Rokicki}
\centerline{\copyright 1988}
\pageno=\contentspagenumber \advance\pageno by 1
@* Introduction.
This report documents the program designed to output \TeX\ processed data to any
ASCII terminal. The program is based on \.{DVIgen} which is intended as a
generic driver for \.{DVI} files created by \TeX82 and other document
preparation systems and much of this documentation is extracted directly from
the Rokicki original. It should be relatively easy to modify for a particular
system.
\vutex\ uses the 95 printable ASCII characters and the rather limited row and
column format of an ordinary screen or printer to mimic somehow the typeset
output of \TeX\ and fine resolution printers. Understandably the result is much
less than other \.{DVIgen} processors, but it is a lot more acessible.
The overall plan of \vutex\ is to scan the \.{DVI} file and to determine what
fonts are being used. For most of these fonts, a table is available which maps
a character in the font to a corresponding printable ASCII character. Then,
with each page, a page grid is created corresponding to the raster grid of the
screen or printer. Each character is placed in a location in that grid
according to the horizontal and vertical coordinates determined by \TeX.
Subscripts or superscripts are associated with their baseline. Finally the page
grid is output to the device in either of two forms --- compressed or \TeX\
spaced. In the compressed mode, all extraneous spaces between words are
deleted, while the alternate mode places words so that the characters are
left-justified in the space and at the location determined by \TeX.
@d clone=='vutex'
@ The |banner| string defined here should be changed whenever \vutex\
gets modified.
@d banner=='This is ',clone,', Version 1.02'
{printed when the program starts}
@ This program is written in standard \PASCAL, except where it is necessary
to use extensions; for example, \vutex\ must read files whose names
are dynamically specified, and that would be impossible in pure \PASCAL.
All places where nonstandard constructions are used have been listed in
the index under ``system dependencies.''
@!@^system dependencies@>
@d othercases == others: {default for cases not listed explicitly}
@d endcases == @+end {follows the default case in an extended |case| statement}
@f othercases == else
@f endcases == end
@ The binary input comes from |dvi_file|, and the final output is written
to |bit_file|. On line interaction and error messages are written
on \PASCAL's standard |output| file. The term |print| is used instead of
|write| when this program writes on |output|, so that all such output
could easily be redirected if desired.
@d print(#)==write(#)
@d print_ln(#)==write_ln(#)
@p program vutex(dvi_file,bit_file,input,output);
label @<Labels in the outer block@>@/
const @<Constants in the outer block@>@/
type @<Types in the outer block@>@/
var @<Globals in the outer block@>@/
procedure initialize; {this procedure gets things started properly}
var i:integer; {loop index for initializations}
begin print_ln(banner);@/
@<Set initial values@>@/
end;
@ If the program has to stop prematurely, it goes to the
`|final_end|'. Another label, |done|, is used when stopping normally.
@d final_end=9999 {label for the end of it all}
@d done=30 {go here when finished with a subtask}
@d pdone=31 {go here when finished with a subtask during prescan}
@<Labels...@>=final_end,done,pdone;
@ The following parameters can be changed at compile time to extend or
reduce \vutex 's capacity.
@<Constants...@>=
@!max_mem_size=68000; {the major array used for almost everything.}
@!name_size=1000; {total length of all font file names, special commands, and
other miscellaneous strings.}
@!terminal_line_length=150; {maximum number of characters input in a single
line of input from the terminal}
@!stack_size=100; {\.{DVI} files shouldn't |push| beyond this depth}
@!name_length=50; {maximum length of a file name}
@!total_rast=56000; {maximum number of rasters on page}
@!max_p_width=280; {maximum width of page in rasters}
@!dfl_p_width=200; {default width of page in rasters}
@!dfl_n_lines=280; {maximum number of lines in a page }
@!dfl_screen_width=80; {default width of screen in rasters}
@!dfl_screen_height=22; {default height of screen in rasters}
@!hor_overlap=6; {overlap for horizontal page action}
@!vert_overlap=2; {overlap for vertical page action}
@!page_length=36; {maximum physical length of a page (in cm)}
@!hh_offset=18; {offset at left side of page}
@!vv_offset=36; {offset at top of page}
@ Here are some macros for common programming idioms.
@d incr(#) == #:=#+1 {increase a variable by unity}
@d decr(#) == #:=#-1 {decrease a variable by unity}
@d do_nothing == {empty statement}
@ If the \.{DVI} file is badly malformed, the whole process must be aborted;
\vutex\ will give up, after issuing an error message about the symptoms
that were noticed.
Such errors might be discovered inside of subroutines inside of subroutines,
so a procedure called |jump_out| has been introduced. This procedure, which
simply transfers control to the label |final_end| at the end of the program,
contains the only non-local |goto| statement in \vutex.
@^system dependencies@>
@d abort(#)==begin print_ln(' ',#); jump_out;
end
@d bad_dvi(#)==abort('Bad DVI file: ',#,'!')
@.Bad DVI file@>
@p procedure jump_out;
begin goto final_end;
@* The character set.
Like all programs written with the \.{WEB} system, \vutex\ can be
used with any character set. But it uses ASCII code internally, because
the programming for portable input-output is easier when a fixed internal
code is used, and because \.{DVI} files use ASCII code for file names
and certain other strings.
@d dfl_chr == '#' {default character ASCII code for unprintable fonts}
@<Types...@>=
@!ASCII_code=" ".."~"; {a subrange of the integers}
@ The original \PASCAL\ compiler was designed in the late 60s, when six-bit
character sets were common, so it did not make provision for lower case
letters. Nowadays, of course, we need to deal with both upper and lower case
alphabets in a convenient way, especially in a program like \vutex,
So we shall assume that the \PASCAL\ system being used for \vutex\
has a character set containing at least the standard visible characters
of ASCII code (|"!"| through |"~"|).
Some \PASCAL\ compilers use the original name |char| for the data type
associated with the characters in text files, while other \PASCAL s
consider |char| to be a 64-element subrange of a larger data type that has
some other name. In order to accommodate this difference, we shall use
the name |text_char| to stand for the data type of the characters in the
output file. We shall also assume that |text_char| consists of
the elements |chr(first_text_char)| through |chr(last_text_char)|,
inclusive. The following definitions should be adjusted if necessary.
@^system dependencies@>
@d text_char == char {the data type of characters in text files}
@d first_text_char=0 {ordinal number of the smallest element of |text_char|}
@d last_text_char=127 {ordinal number of the largest element of |text_char|}
@<Types...@>=
@!text_file=packed file of text_char;
@ The \vutex\ processor converts between ASCII code and the user's external
character set by means of arrays |xord| and |xchr| that are analogous to
\PASCAL's |ord| and |chr| functions. However, the fonts used by \TeX\ do not
all use the same character table and so we define other arrays to handle output
for the major font families.
@<Globals...@>=
@!xord: array [text_char] of ASCII_code;
{specifies conversion of input characters}
@!xchr: array [0..255] of text_char;
{specifies conversion of output characters for tty fonts}
@!rchr: array [0..255] of text_char;
{specifies conversion of output characters for roman type fonts}
@!mchr: array [0..255] of text_char;
{specifies conversion of output characters for math italic fonts}
@!schr: array [0..255] of text_char;
{specifies conversion of output characters for math symbol fonts}
@!echr: array [0..255] of text_char;
{specifies conversion of output characters for math extension fonts}
@ Under our assumption that the visible characters of standard ASCII are
all present, the following assignment statements initialize the
|xchr| array properly, without needing any system-dependent changes.
@<Set init...@>=
for i:=0 to @'12 do xchr[i]:=dfl_chr;
xchr[@'13]:='^';
xchr[@'14]:='v';
xchr[@'15]:='''';
xchr[@'16]:=dfl_chr;
xchr[@'17]:=dfl_chr;@/
xchr[@'20]:='i';
xchr[@'21]:='j';
xchr[@'22]:='`';
xchr[@'23]:='''';
xchr[@'24]:=' ';
xchr[@'25]:=' ';
xchr[@'26]:=' ';
xchr[@'27]:=' ';@/
xchr[@'30]:=' ';
xchr[@'31]:=dfl_chr;
xchr[@'32]:=dfl_chr;
xchr[@'33]:=dfl_chr;
xchr[@'34]:=dfl_chr;
xchr[@'35]:=dfl_chr;
xchr[@'36]:=dfl_chr;
xchr[@'37]:=dfl_chr;
xchr[@'40]:=' ';
xchr[@'41]:='!';
xchr[@'42]:='"';
xchr[@'43]:='#';
xchr[@'44]:='$';
xchr[@'45]:='%';
xchr[@'46]:='&';
xchr[@'47]:='''';@/
xchr[@'50]:='(';
xchr[@'51]:=')';
xchr[@'52]:='*';
xchr[@'53]:='+';
xchr[@'54]:=',';
xchr[@'55]:='-';
xchr[@'56]:='.';
xchr[@'57]:='/';@/
xchr[@'60]:='0';
xchr[@'61]:='1';
xchr[@'62]:='2';
xchr[@'63]:='3';
xchr[@'64]:='4';
xchr[@'65]:='5';
xchr[@'66]:='6';
xchr[@'67]:='7';@/
xchr[@'70]:='8';
xchr[@'71]:='9';
xchr[@'72]:=':';
xchr[@'73]:=';';
xchr[@'74]:='<';
xchr[@'75]:='=';
xchr[@'76]:='>';
xchr[@'77]:='?';@/
xchr[@'100]:='@@';
xchr[@'101]:='A';
xchr[@'102]:='B';
xchr[@'103]:='C';
xchr[@'104]:='D';
xchr[@'105]:='E';
xchr[@'106]:='F';
xchr[@'107]:='G';@/
xchr[@'110]:='H';
xchr[@'111]:='I';
xchr[@'112]:='J';
xchr[@'113]:='K';
xchr[@'114]:='L';
xchr[@'115]:='M';
xchr[@'116]:='N';
xchr[@'117]:='O';@/
xchr[@'120]:='P';
xchr[@'121]:='Q';
xchr[@'122]:='R';
xchr[@'123]:='S';
xchr[@'124]:='T';
xchr[@'125]:='U';
xchr[@'126]:='V';
xchr[@'127]:='W';@/
xchr[@'130]:='X';
xchr[@'131]:='Y';
xchr[@'132]:='Z';
xchr[@'133]:='[';
xchr[@'134]:='\';
xchr[@'135]:=']';
xchr[@'136]:='^';
xchr[@'137]:='_';@/
xchr[@'140]:='`';
xchr[@'141]:='a';
xchr[@'142]:='b';
xchr[@'143]:='c';
xchr[@'144]:='d';
xchr[@'145]:='e';
xchr[@'146]:='f';
xchr[@'147]:='g';@/
xchr[@'150]:='h';
xchr[@'151]:='i';
xchr[@'152]:='j';
xchr[@'153]:='k';
xchr[@'154]:='l';
xchr[@'155]:='m';
xchr[@'156]:='n';
xchr[@'157]:='o';@/
xchr[@'160]:='p';
xchr[@'161]:='q';
xchr[@'162]:='r';
xchr[@'163]:='s';
xchr[@'164]:='t';
xchr[@'165]:='u';
xchr[@'166]:='v';
xchr[@'167]:='w';@/
xchr[@'170]:='x';
xchr[@'171]:='y';
xchr[@'172]:='z';
xchr[@'173]:='{';
xchr[@'174]:='|';
xchr[@'175]:='}';
xchr[@'176]:='~';
for i:=@'177 to 255 do xchr[i]:=dfl_chr;
@ Many of the \TeX\ standard fonts use a character layout slightly different
from that given above. Therefore, we define a new array to handle output
from these fonts. Modern Roman fonts are given below.
@<Set init...@>=
for i:=0 to @'17 do rchr[i]:=dfl_chr;
rchr[@'20]:='i';
rchr[@'21]:='j';
rchr[@'22]:='`';
rchr[@'23]:='''';
rchr[@'24]:=' ';
rchr[@'25]:=' ';
rchr[@'26]:=' ';
rchr[@'27]:=' ';@/
rchr[@'30]:=' ';
rchr[@'31]:='s';
rchr[@'32]:='a';
rchr[@'33]:='o';
rchr[@'34]:='o';
rchr[@'35]:='A';
rchr[@'36]:='O';
rchr[@'37]:='O';@/
rchr[@'40]:=' ';
rchr[@'41]:='!';
rchr[@'42]:='"';
rchr[@'43]:='#';
rchr[@'44]:='$';
rchr[@'45]:='%';
rchr[@'46]:='&';
rchr[@'47]:='''';@/
rchr[@'50]:='(';
rchr[@'51]:=')';
rchr[@'52]:='*';
rchr[@'53]:='+';
rchr[@'54]:=',';
rchr[@'55]:='-';
rchr[@'56]:='.';
rchr[@'57]:='/';@/
rchr[@'60]:='0';
rchr[@'61]:='1';
rchr[@'62]:='2';
rchr[@'63]:='3';
rchr[@'64]:='4';
rchr[@'65]:='5';
rchr[@'66]:='6';
rchr[@'67]:='7';@/
rchr[@'70]:='8';
rchr[@'71]:='9';
rchr[@'72]:=':';
rchr[@'73]:=';';
rchr[@'74]:=dfl_chr;
rchr[@'75]:='=';
rchr[@'76]:=dfl_chr;
rchr[@'77]:='?';@/
rchr[@'100]:='@@';
rchr[@'101]:='A';
rchr[@'102]:='B';
rchr[@'103]:='C';
rchr[@'104]:='D';
rchr[@'105]:='E';
rchr[@'106]:='F';
rchr[@'107]:='G';@/
rchr[@'110]:='H';
rchr[@'111]:='I';
rchr[@'112]:='J';
rchr[@'113]:='K';
rchr[@'114]:='L';
rchr[@'115]:='M';
rchr[@'116]:='N';
rchr[@'117]:='O';@/
rchr[@'120]:='P';
rchr[@'121]:='Q';
rchr[@'122]:='R';
rchr[@'123]:='S';
rchr[@'124]:='T';
rchr[@'125]:='U';
rchr[@'126]:='V';
rchr[@'127]:='W';@/
rchr[@'130]:='X';
rchr[@'131]:='Y';
rchr[@'132]:='Z';
rchr[@'133]:='[';
rchr[@'134]:='"';
rchr[@'135]:=']';
rchr[@'136]:='^';
rchr[@'137]:=' ';@/
rchr[@'140]:='`';
rchr[@'141]:='a';
rchr[@'142]:='b';
rchr[@'143]:='c';
rchr[@'144]:='d';
rchr[@'145]:='e';
rchr[@'146]:='f';
rchr[@'147]:='g';@/
rchr[@'150]:='h';
rchr[@'151]:='i';
rchr[@'152]:='j';
rchr[@'153]:='k';
rchr[@'154]:='l';
rchr[@'155]:='m';
rchr[@'156]:='n';
rchr[@'157]:='o';@/
rchr[@'160]:='p';
rchr[@'161]:='q';
rchr[@'162]:='r';
rchr[@'163]:='s';
rchr[@'164]:='t';
rchr[@'165]:='u';
rchr[@'166]:='v';
rchr[@'167]:='w';@/
rchr[@'170]:='x';
rchr[@'171]:='y';
rchr[@'172]:='z';
rchr[@'173]:='-';
rchr[@'174]:='-';
rchr[@'175]:='"';
rchr[@'176]:='~';
for i:=@'177 to 255 do rchr[i]:=dfl_chr;
@ The standard \TeX\ math italics fonts use quite a different layout than
either of the above, and we include a separate array for these useful fonts.
@<Set init...@>=
for i:=0 to @'47 do mchr[i]:=dfl_chr;
mchr[@'50]:='-';
mchr[@'51]:='-';
mchr[@'52]:='-';
mchr[@'53]:='-';
mchr[@'54]:=' ';
mchr[@'55]:=' ';
mchr[@'56]:='>';
mchr[@'57]:='<';@/
mchr[@'60]:='0';
mchr[@'61]:='1';
mchr[@'62]:='2';
mchr[@'63]:='3';
mchr[@'64]:='4';
mchr[@'65]:='5';
mchr[@'66]:='6';
mchr[@'67]:='7';@/
mchr[@'70]:='8';
mchr[@'71]:='9';
mchr[@'72]:='.';
mchr[@'73]:=',';
mchr[@'74]:='<';
mchr[@'75]:='/';
mchr[@'76]:='>';
mchr[@'77]:='*';@/
mchr[@'100]:=dfl_chr;
mchr[@'101]:='A';
mchr[@'102]:='B';
mchr[@'103]:='C';
mchr[@'104]:='D';
mchr[@'105]:='E';
mchr[@'106]:='F';
mchr[@'107]:='G';@/
mchr[@'110]:='H';
mchr[@'111]:='I';
mchr[@'112]:='J';
mchr[@'113]:='K';
mchr[@'114]:='L';
mchr[@'115]:='M';
mchr[@'116]:='N';
mchr[@'117]:='O';@/
mchr[@'120]:='P';
mchr[@'121]:='Q';
mchr[@'122]:='R';
mchr[@'123]:='S';
mchr[@'124]:='T';
mchr[@'125]:='U';
mchr[@'126]:='V';
mchr[@'127]:='W';@/
mchr[@'130]:='X';
mchr[@'131]:='Y';
mchr[@'132]:='Z';
mchr[@'133]:=dfl_chr;
mchr[@'134]:=dfl_chr;
mchr[@'135]:=dfl_chr;
mchr[@'136]:=dfl_chr;
mchr[@'137]:=dfl_chr;@/
mchr[@'140]:='l';
mchr[@'141]:='a';
mchr[@'142]:='b';
mchr[@'143]:='c';
mchr[@'144]:='d';
mchr[@'145]:='e';
mchr[@'146]:='f';
mchr[@'147]:='g';@/
mchr[@'150]:='h';
mchr[@'151]:='i';
mchr[@'152]:='j';
mchr[@'153]:='k';
mchr[@'154]:='l';
mchr[@'155]:='m';
mchr[@'156]:='n';
mchr[@'157]:='o';@/
mchr[@'160]:='p';
mchr[@'161]:='q';
mchr[@'162]:='r';
mchr[@'163]:='s';
mchr[@'164]:='t';
mchr[@'165]:='u';
mchr[@'166]:='v';
mchr[@'167]:='w';@/
mchr[@'170]:='x';
mchr[@'171]:='y';
mchr[@'172]:='z';
mchr[@'173]:='i';
mchr[@'174]:='j';
mchr[@'175]:='p';
mchr[@'176]:=' ';
mchr[@'177]:=' ';@/
for i:=@'200 to 255 do mchr[i]:=dfl_chr;
@ The math symbol font is, for the most part, unprintable in ASCII, but we
shall map some of the characters onto meaningful representations.
@<Set init...@>=
schr[0]:='-';
schr[1]:='.';
schr[2]:='x';
schr[3]:='*';
schr[4]:=':';
schr[5]:=dfl_chr;
schr[6]:='+';
schr[7]:='-';@/
schr[@'10]:='+';
schr[@'11]:='-';
schr[@'12]:='x';
schr[@'13]:='o';
schr[@'14]:='o';
schr[@'15]:='O';
schr[@'16]:='o';
schr[@'17]:='o';@/
schr[@'20]:='=';
schr[@'21]:='=';
schr[@'22]:='<';
schr[@'23]:='>';
schr[@'24]:='<';
schr[@'25]:='>';
schr[@'26]:='<';
schr[@'27]:='>';@/
schr[@'30]:='~';
schr[@'31]:='~';
schr[@'32]:='<';
schr[@'33]:='>';
schr[@'34]:='<';
schr[@'35]:='>';
schr[@'36]:='<';
schr[@'37]:='>';@/
schr[@'40]:='-';
schr[@'41]:='-';
schr[@'42]:='|';
schr[@'43]:='|';
schr[@'44]:='-';
schr[@'45]:='/';
schr[@'46]:='\';
schr[@'47]:='~';@/
schr[@'50]:='-';
schr[@'51]:='-';
schr[@'52]:='|';
schr[@'53]:='|';
schr[@'54]:='-';
schr[@'55]:='\';
schr[@'56]:='/';
schr[@'57]:=dfl_chr;@/
schr[@'60]:='''';
schr[@'61]:=dfl_chr;
schr[@'62]:=dfl_chr;
schr[@'63]:=dfl_chr;
schr[@'64]:=dfl_chr;
schr[@'65]:=dfl_chr;
schr[@'66]:='/';
schr[@'67]:=dfl_chr;@/
schr[@'70]:=dfl_chr;
schr[@'71]:=dfl_chr;
schr[@'72]:='-';
schr[@'73]:='0';
schr[@'74]:='R';
schr[@'75]:='I';
schr[@'76]:='|';
schr[@'77]:='|';@/
schr[@'100]:=dfl_chr;
schr[@'101]:='A';
schr[@'102]:='B';
schr[@'103]:='C';
schr[@'104]:='D';
schr[@'105]:='E';
schr[@'106]:='F';
schr[@'107]:='G';@/
schr[@'110]:='H';
schr[@'111]:='I';
schr[@'112]:='J';
schr[@'113]:='K';
schr[@'114]:='L';
schr[@'115]:='M';
schr[@'116]:='N';
schr[@'117]:='O';@/
schr[@'120]:='P';
schr[@'121]:='Q';
schr[@'122]:='R';
schr[@'123]:='S';
schr[@'124]:='T';
schr[@'125]:='U';
schr[@'126]:='V';
schr[@'127]:='W';@/
schr[@'130]:='X';
schr[@'131]:='Y';
schr[@'132]:='Z';
schr[@'133]:=dfl_chr;
schr[@'134]:=dfl_chr;
schr[@'135]:=dfl_chr;
schr[@'136]:=dfl_chr;
schr[@'137]:=dfl_chr;@/
schr[@'140]:='-';
schr[@'141]:='-';
schr[@'142]:='|';
schr[@'143]:='|';
schr[@'144]:='|';
schr[@'145]:='|';
schr[@'146]:='{';
schr[@'147]:='}';@/
schr[@'150]:='<';
schr[@'151]:='>';
schr[@'152]:='|';
schr[@'153]:='|';
schr[@'154]:='|';
schr[@'155]:='|';
schr[@'156]:='\';
schr[@'157]:=dfl_chr;@/
schr[@'160]:='|';
schr[@'161]:=dfl_chr;
schr[@'162]:=dfl_chr;
schr[@'163]:='S';
schr[@'164]:=dfl_chr;
schr[@'165]:=dfl_chr;
schr[@'166]:='<';
schr[@'167]:='>';@/
for i:=@'170 to @'176 do schr[i]:=dfl_chr;
for i:=@'177 to 255 do schr[i]:=dfl_chr;
@ And then there is the math extension font which is used to create extended
symbols. We do the best that we can.
@<Set init...@>=
echr[0]:='(';
echr[1]:=')';
echr[2]:='[';
echr[3]:=']';
echr[4]:='|';
echr[5]:='|';
echr[6]:='|';
echr[7]:='|';@/
echr[@'10]:='{';
echr[@'11]:='}';
echr[@'12]:='<';
echr[@'13]:='>';
echr[@'14]:='|';
echr[@'15]:='|';
echr[@'16]:='/';
echr[@'17]:='\';@/
echr[@'20]:='(';
echr[@'21]:=')';
echr[@'22]:='(';
echr[@'23]:=')';
echr[@'24]:='[';
echr[@'25]:=']';
echr[@'26]:='|';
echr[@'27]:='|';@/
echr[@'30]:='|';
echr[@'31]:='|';
echr[@'32]:='{';
echr[@'33]:='}';
echr[@'34]:='<';
echr[@'35]:='>';
echr[@'36]:='/';
echr[@'37]:='\';@/
echr[@'40]:='(';
echr[@'41]:=')';
echr[@'42]:='[';
echr[@'43]:=']';
echr[@'44]:='|';
echr[@'45]:='|';
echr[@'46]:='|';
echr[@'47]:='|';@/
echr[@'50]:='{';
echr[@'51]:='}';
echr[@'52]:='<';
echr[@'53]:='>';
echr[@'54]:='/';
echr[@'55]:='\';
echr[@'56]:='/';
echr[@'57]:='\';@/
echr[@'60]:='(';
echr[@'61]:=')';
echr[@'62]:='|';
echr[@'63]:='|';
echr[@'64]:='|';
echr[@'65]:='|';
echr[@'66]:='|';
echr[@'67]:='|';@/
echr[@'70]:='(';
echr[@'71]:=')';
echr[@'72]:='(';
echr[@'73]:=')';
echr[@'74]:='{';
echr[@'75]:='}';
echr[@'76]:='|';
echr[@'77]:='|';@/
echr[@'100]:='(';
echr[@'101]:=')';
echr[@'102]:='|';
echr[@'103]:='|';
echr[@'104]:='<';
echr[@'105]:='>';
echr[@'106]:=dfl_chr;
echr[@'107]:=dfl_chr;@/
echr[@'110]:='S';
echr[@'111]:='S';
echr[@'112]:='o';
echr[@'113]:='O';
echr[@'114]:='+';
echr[@'115]:='+';
echr[@'116]:='x';
echr[@'117]:='X';@/
echr[@'120]:='E';
echr[@'121]:=dfl_chr;
echr[@'122]:='S';
for i:=@'123 to @'127 do echr[i]:=dfl_chr;
echr[@'130]:='E';
echr[@'131]:=dfl_chr;
echr[@'132]:='S';
for i:=@'133 to @'141 do echr[i]:=dfl_chr;
echr[@'142]:='^';
echr[@'143]:='^';
echr[@'144]:='^';
echr[@'145]:='~';
echr[@'146]:='~';
echr[@'147]:='~';@/
echr[@'150]:='[';
echr[@'151]:=']';
echr[@'152]:='|';
echr[@'153]:='|';
echr[@'154]:='|';
echr[@'155]:='|';
echr[@'156]:='{';
echr[@'157]:='}';@/
echr[@'160]:='|';
echr[@'161]:='|';
echr[@'162]:='|';
echr[@'163]:='|';
echr[@'164]:='|';
echr[@'165]:='|';
echr[@'166]:='|';
echr[@'167]:='|';@/
echr[@'170]:='|';
echr[@'171]:='\';
echr[@'172]:=dfl_chr;
echr[@'173]:=dfl_chr;
echr[@'174]:=dfl_chr;
echr[@'175]:=dfl_chr;
echr[@'176]:='|';
echr[@'177]:='|';@/
for i:=@'200 to 255 do echr[i]:=dfl_chr;
@ The following system-independent code makes the |xord| array contain a
suitable inverse to the information in |xchr|.
@<Set init...@>=
for i:=first_text_char to last_text_char do xord[chr(i)]:=@'40;
for i:=" " to "~" do xord[xchr[i]]:=i;
@* Device-independent file format.
Before we get into the details of \vutex, we need to know exactly
what \.{DVI} files are. The form of such files was designed by David R.
@^Fuchs, David Raymond@>
Fuchs in 1979. Almost any reasonable typesetting device can be driven by
a program that takes \.{DVI} files as input, and dozens of such
\.{DVI}-to-whatever programs have been written. Thus, it is possible to
print the output of document compilers like \TeX\ on many different kinds
of equipment.
A \.{DVI} file is a stream of 8-bit bytes, which may be regarded as a
series of commands in a machine-like language. The first byte of each command
is the operation code, and this code is followed by zero or more bytes
that provide parameters to the command. The parameters themselves may consist
of several consecutive bytes; for example, the `|set_rule|' command has two
parameters, each of which is four bytes long. Parameters are usually
regarded as nonnegative integers; but four-byte-long parameters,
and shorter parameters that denote distances, can be
either positive or negative. Such parameters are given in two's complement
notation. For example, a two-byte-long distance parameter has a value between
$-2^{15}$ and $2^{15}-1$.
@.DVI {\rm files}@>
A \.{DVI} file consists of a ``preamble,'' followed by a sequence of one
or more ``pages,'' followed by a ``postamble.'' The preamble is simply a
|pre| command, with its parameters that define the dimensions used in the
file; this must come first. Each ``page'' consists of a |bop| command,
followed by any number of other commands that tell where characters are to
be placed on a physical page, followed by an |eop| command. The pages
appear in the order that they were generated, not in any particular
numerical order. If we ignore |nop| commands and \\{fnt\_def} commands
(which are allowed between any two commands in the file), each |eop|
command is immediately followed by a |bop| command, or by a |post|
command; in the latter case, there are no more pages in the file, and the
remaining bytes form the postamble. Further details about the postamble
will be explained later.
Some parameters in \.{DVI} commands are ``pointers.'' These are four-byte
quantities that give the location number of some other byte in the file;
the first byte is number~0, then comes number~1, and so on. For example,
one of the parameters of a |bop| command points to the previous |bop|;
this makes it feasible to read the pages in backwards order, in case the
results are being directed to a device that stacks its output face up.
Suppose the preamble of a \.{DVI} file occupies bytes 0 to 99. Now if the
first page occupies bytes 100 to 999, say, and if the second
page occupies bytes 1000 to 1999, then the |bop| that starts in byte 1000
points to 100 and the |bop| that starts in byte 2000 points to 1000. (The
very first |bop|, i.e., the one that starts in byte 100, has a pointer of $-1$.)
@ The \.{DVI} format is intended to be both compact and easily interpreted
by a machine. Compactness is achieved by making most of the information
implicit instead of explicit. When a \.{DVI}-reading program reads the
commands for a page, it keeps track of several quantities: (a)~The current
font |f| is an integer; this value is changed only
by \\{fnt} and \\{fnt\_num} commands. (b)~The current position on the page
is given by two numbers called the horizontal and vertical coordinates,
|h| and |v|. Both coordinates are zero at the upper left corner of the page;
moving to the right corresponds to increasing the horizontal coordinate, and
moving down corresponds to increasing the vertical coordinate. Thus, the
coordinates are essentially Cartesian, except that vertical directions are
flipped; the Cartesian version of |(h,v)| would be |(h,-v)|. (c)~The
current spacing amounts are given by four numbers |w|, |x|, |y|, and |z|,
where |w| and~|x| are used for horizontal spacing and where |y| and~|z|
are used for vertical spacing. (d)~There is a stack containing
|(h,v,w,x,y,z)| values; the \.{DVI} commands |push| and |pop| are used to
change the current level of operation. Note that the current font~|f| is
not pushed and popped; the stack contains only information about
positioning.
The values of |h|, |v|, |w|, |x|, |y|, and |z| are signed integers having up
to 32 bits, including the sign. Since they represent physical distances,
there is a small unit of measurement such that increasing |h| by~1 means
moving a certain tiny distance to the right. The actual unit of
measurement is variable, as explained below.
@ Here is a list of all the commands that may appear in a \.{DVI} file. Each
command is specified by its symbolic name (e.g., |bop|), its opcode byte
(e.g., 139), and its parameters (if any). The parameters are followed
by a bracketed number telling how many bytes they occupy; for example,
`|p[4]|' means that parameter |p| is four bytes long.
\yskip\hang|set_char_0| 0. Typeset character number~0 from font~|f|
such that the reference point of the character is at |(h,v)|. Then
increase |h| by the width of that character. Note that a character may
have zero or negative width, so one cannot be sure that |h| will advance
after this command; but |h| usually does increase.
\yskip\hang|set_char_1| through |set_char_127| (opcodes 1 to 127).
Do the operations of |set_char_0|; but use the character whose number
matches the opcode, instead of character~0.
\yskip\hang|set1| 128 |c[1]|. Same as |set_char_0|, except that character
number~|c| is typeset. \TeX82 uses this command for characters in the
range |128<=c<256|.
\yskip\hang|set2| 129 |c[2]|. Same as |set1|, except that |c|~is two
bytes long, so it is in the range |0<=c<65536|. \TeX82 never uses this
command, which is intended for processors that deal with oriental languages;
but \vutex\ will allow character codes greater than 255, assuming that
they all have the same width as the character whose code is $c \bmod 256$.
@^oriental characters@>@^Chinese characters@>@^Japanese characters@>
\yskip\hang|set3| 130 |c[3]|. Same as |set1|, except that |c|~is three
bytes long, so it can be as large as $2^{24}-1$.
\yskip\hang|set4| 131 |c[4]|. Same as |set1|, except that |c|~is four
bytes long, possibly even negative. Imagine that.
\yskip\hang|set_rule| 132 |a[4]| |b[4]|. Typeset a solid black rectangle
of height |a| and width |b|, with its bottom left corner at |(h,v)|. Then
set |h:=h+b|. If either |a<=0| or |b<=0|, nothing should be typeset. Note
that if |b<0|, the value of |h| will decrease even though nothing else happens.
Programs that typeset from \.{DVI} files should be careful to make the rules
line up carefully with digitized characters, as explained in connection with
the |rule_pixels| subroutine below.
\yskip\hang|put1| 133 |c[1]|. Typeset character number~|c| from font~|f|
such that the reference point of the character is at |(h,v)|. (The `put'
commands are exactly like the `set' commands, except that they simply put out a
character or a rule without moving the reference point afterwards.)
\yskip\hang|put2| 134 |c[2]|. Same as |set2|, except that |h| is not changed.
\yskip\hang|put3| 135 |c[3]|. Same as |set3|, except that |h| is not changed.
\yskip\hang|put4| 136 |c[4]|. Same as |set4|, except that |h| is not changed.
\yskip\hang|put_rule| 137 |a[4]| |b[4]|. Same as |set_rule|, except that
|h| is not changed.
\yskip\hang|nop| 138. No operation, do nothing. Any number of |nop|'s
may occur between \.{DVI} commands, but a |nop| cannot be inserted between
a command and its parameters or between two parameters.
\yskip\hang|bop| 139 $c_0[4]$ $c_1[4]$ $\ldots$ $c_9[4]$ $p[4]$. Beginning
of a page: Set |(h,v,w,x,y,z):=(0,0,0,0,0,0)| and set the stack empty. Set
the current font |f| to an undefined value. The ten $c_i$ parameters can
be used to identify pages, if a user wants to print only part of a \.{DVI}
file; \TeX82 gives them the values of \.{\\count0} $\ldots$ \.{\\count9}
at the time \.{\\shipout} was invoked for this page. The parameter |p|
points to the previous |bop| command in the file, where the first |bop|
has $p=-1$.
\yskip\hang|eop| 140. End of page: Print what you have read since the
previous |bop|. At this point the stack should be empty. (The \.{DVI}-reading
programs that drive most output devices will have kept a buffer of the
material that appears on the page that has just ended. This material is
largely, but not entirely, in order by |v| coordinate and (for fixed |v|) by
|h|~coordinate; so it usually needs to be sorted into some order that is
appropriate for the device in question. \vutex\ does not do such sorting.)
\yskip\hang|push| 141. Push the current values of |(h,v,w,x,y,z)| onto the
top of the stack; do not change any of these values. Note that |f| is
not pushed.
\yskip\hang|pop| 142. Pop the top six values off of the stack and assign
them to |(h,v,w,x,y,z)|. The number of pops should never exceed the number
of pushes, since it would be highly embarrassing if the stack were empty
at the time of a |pop| command.
\yskip\hang|right1| 143 |b[1]|. Set |h:=h+b|, i.e., move right |b| units.
The parameter is a signed number in two's complement notation, |-128<=b<128|;
if |b<0|, the reference point actually moves left.
\yskip\hang|right2| 144 |b[2]|. Same as |right1|, except that |b| is a
two-byte quantity in the range |-32768<=b<32768|.
\yskip\hang|right3| 145 |b[3]|. Same as |right1|, except that |b| is a
three-byte quantity in the range |@t$-2^{23}$@><=b<@t$2^{23}$@>|.
\yskip\hang|right4| 146 |b[4]|. Same as |right1|, except that |b| is a
four-byte quantity in the range |@t$-2^{31}$@><=b<@t$2^{31}$@>|.
\yskip\hang|w0| 147. Set |h:=h+w|; i.e., move right |w| units. With luck,
this parameterless command will usually suffice, because the same kind of motion
will occur several times in succession; the following commands explain how
|w| gets particular values.
\yskip\hang|w1| 148 |b[1]|. Set |w:=b| and |h:=h+b|. The value of |b| is a
signed quantity in two's complement notation, |-128<=b<128|. This command
changes the current |w|~spacing and moves right by |b|.
\yskip\hang|w2| 149 |b[2]|. Same as |w1|, but |b| is a two-byte-long
parameter, |-32768<=b<32768|.
\yskip\hang|w3| 150 |b[3]|. Same as |w1|, but |b| is a three-byte-long
parameter, |@t$-2^{23}$@><=b<@t$2^{23}$@>|.
\yskip\hang|w4| 151 |b[4]|. Same as |w1|, but |b| is a four-byte-long
parameter, |@t$-2^{31}$@><=b<@t$2^{31}$@>|.
\yskip\hang|x0| 152. Set |h:=h+x|; i.e., move right |x| units. The `|x|'
commands are like the `|w|' commands except that they involve |x| instead
of |w|.
\yskip\hang|x1| 153 |b[1]|. Set |x:=b| and |h:=h+b|. The value of |b| is a
signed quantity in two's complement notation, |-128<=b<128|. This command
changes the current |x|~spacing and moves right by |b|.
\yskip\hang|x2| 154 |b[2]|. Same as |x1|, but |b| is a two-byte-long
parameter, |-32768<=b<32768|.
\yskip\hang|x3| 155 |b[3]|. Same as |x1|, but |b| is a three-byte-long
parameter, |@t$-2^{23}$@><=b<@t$2^{23}$@>|.
\yskip\hang|x4| 156 |b[4]|. Same as |x1|, but |b| is a four-byte-long
parameter, |@t$-2^{31}$@><=b<@t$2^{31}$@>|.
\yskip\hang|down1| 157 |a[1]|. Set |v:=v+a|, i.e., move down |a| units.
The parameter is a signed number in two's complement notation, |-128<=a<128|;
if |a<0|, the reference point actually moves up.
\yskip\hang|down2| 158 |a[2]|. Same as |down1|, except that |a| is a
two-byte quantity in the range |-32768<=a<32768|.
\yskip\hang|down3| 159 |a[3]|. Same as |down1|, except that |a| is a
three-byte quantity in the range |@t$-2^{23}$@><=a<@t$2^{23}$@>|.
\yskip\hang|down4| 160 |a[4]|. Same as |down1|, except that |a| is a
four-byte quantity in the range |@t$-2^{31}$@><=a<@t$2^{31}$@>|.
\yskip\hang|y0| 161. Set |v:=v+y|; i.e., move down |y| units. With luck,
this parameterless command will usually suffice, because the same kind of motion
will occur several times in succession; the following commands explain how
|y| gets particular values.
\yskip\hang|y1| 162 |a[1]|. Set |y:=a| and |v:=v+a|. The value of |a| is a
signed quantity in two's complement notation, |-128<=a<128|. This command
changes the current |y|~spacing and moves down by |a|.
\yskip\hang|y2| 163 |a[2]|. Same as |y1|, but |a| is a two-byte-long
parameter, |-32768<=a<32768|.
\yskip\hang|y3| 164 |a[3]|. Same as |y1|, but |a| is a three-byte-long
parameter, |@t$-2^{23}$@><=a<@t$2^{23}$@>|.
\yskip\hang|y4| 165 |a[4]|. Same as |y1|, but |a| is a four-byte-long
parameter, |@t$-2^{31}$@><=a<@t$2^{31}$@>|.
\yskip\hang|z0| 166. Set |v:=v+z|; i.e., move down |z| units. The `|z|' commands
are like the `|y|' commands except that they involve |z| instead of |y|.
\yskip\hang|z1| 167 |a[1]|. Set |z:=a| and |v:=v+a|. The value of |a| is a
signed quantity in two's complement notation, |-128<=a<128|. This command
changes the current |z|~spacing and moves down by |a|.
\yskip\hang|z2| 168 |a[2]|. Same as |z1|, but |a| is a two-byte-long
parameter, |-32768<=a<32768|.
\yskip\hang|z3| 169 |a[3]|. Same as |z1|, but |a| is a three-byte-long
parameter, |@t$-2^{23}$@><=a<@t$2^{23}$@>|.
\yskip\hang|z4| 170 |a[4]|. Same as |z1|, but |a| is a four-byte-long
parameter, |@t$-2^{31}$@><=a<@t$2^{31}$@>|.
\yskip\hang|fnt_num_0| 171. Set |f:=0|. Font 0 must previously have been
defined by a \\{fnt\_def} instruction, as explained below.
\yskip\hang|fnt_num_1| through |fnt_num_63| (opcodes 172 to 234). Set
|f:=1|, \dots, |f:=63|, respectively.
\yskip\hang|fnt1| 235 |k[1]|. Set |f:=k|. \TeX82 uses this command for font
numbers in the range |64<=k<256|.
\yskip\hang|fnt2| 236 |k[2]|. Same as |fnt1|, except that |k|~is two
bytes long, so it is in the range |0<=k<65536|. \TeX82 never generates this
command, but large font numbers may prove useful for specifications of
color or texture, or they may be used for special fonts that have fixed
numbers in some external coding scheme.
\yskip\hang|fnt3| 237 |k[3]|. Same as |fnt1|, except that |k|~is three
bytes long, so it can be as large as $2^{24}-1$.
\yskip\hang|fnt4| 238 |k[4]|. Same as |fnt1|, except that |k|~is four
bytes long; this is for the really big font numbers (and for the negative ones).
\yskip\hang|xxx1| 239 |k[1]| |x[k]|. This command is undefined in
general; it functions as a $(k+2)$-byte |nop| unless special \.{DVI}-reading
programs are being used. \TeX82 generates |xxx1| when a short enough
\.{\\special} appears, setting |k| to the number of bytes being sent. It
is recommended that |x| be a string having the form of a keyword followed
by possible parameters relevant to that keyword.
\yskip\hang|xxx2| 240 |k[2]| |x[k]|. Like |xxx1|, but |0<=k<65536|.
\yskip\hang|xxx3| 241 |k[3]| |x[k]|. Like |xxx1|, but |0<=k<@t$2^{24}$@>|.
\yskip\hang|xxx4| 242 |k[4]| |x[k]|. Like |xxx1|, but |k| can be ridiculously
large. \TeX82 uses |xxx4| when |xxx1| would be incorrect.
\yskip\hang|fnt_def1| 243 |k[1]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
Define font |k|, where |0<=k<256|; font definitions will be explained shortly.
\yskip\hang|fnt_def2| 244 |k[2]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
Define font |k|, where |0<=k<65536|.
\yskip\hang|fnt_def3| 245 |k[3]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
Define font |k|, where |0<=k<@t$2^{24}$@>|.
\yskip\hang|fnt_def4| 246 |k[4]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.
Define font |k|, where |@t$-2^{31}$@><=k<@t$2^{31}$@>|.
\yskip\hang|pre| 247 |i[1]| |num[4]| |den[4]| |mag[4]| |k[1]| |x[k]|.
Beginning of the preamble; this must come at the very beginning of the
file. Parameters |i|, |num|, |den|, |mag|, |k|, and |x| are explained below.
\yskip\hang|post| 248. Beginning of the postamble, see below.
\yskip\hang|post_post| 249. Ending of the postamble, see below.
\yskip\noindent Commands 250--255 are undefined at the present time.
@ @d set_char_0=0 {typeset character 0 and move right}
@d set1=128 {typeset a character and move right}
@d set_rule=132 {typeset a rule and move right}
@d put1=133 {typeset a character}
@d put_rule=137 {typeset a rule}
@d nop=138 {no operation}
@d bop=139 {beginning of page}
@d eop=140 {ending of page}
@d push=141 {save the current positions}
@d pop=142 {restore previous positions}
@d right1=143 {move right}
@d w0=147 {move right by |w|}
@d w1=148 {move right and set |w|}
@d x0=152 {move right by |x|}
@d x1=153 {move right and set |x|}
@d down1=157 {move down}
@d y0=161 {move down by |y|}
@d y1=162 {move down and set |y|}
@d z0=166 {move down by |z|}
@d z1=167 {move down and set |z|}
@d fnt_num_0=171 {set current font to 0}
@d fnt1=235 {set current font}
@d xxx1=239 {extension to \.{DVI} primitives}
@d xxx4=242 {potentially long extension to \.{DVI} primitives}
@d fnt_def1=243 {define the meaning of a font number}
@d pre=247 {preamble}
@d post=248 {postamble beginning}
@d post_post=249 {postamble ending}
@d undefined_commands==250,251,252,253,254,255
@ The preamble contains basic information about the file as a whole. As
stated above, there are six parameters:
$$\hbox{|@!i[1]| |@!num[4]| |@!den[4]| |@!mag[4]| |@!k[1]| |@!x[k]|.}$$
The |i| byte identifies \.{DVI} format; currently this byte is always set
to~2. (Some day we will set |i=3|, when \.{DVI} format makes another
incompatible change---perhaps in 1992.)
The next two parameters, |num| and |den|, are positive integers that define
the units of measurement; they are the numerator and denominator of a
fraction by which all dimensions in the \.{DVI} file could be multiplied
in order to get lengths in units of $10^{-7}$ meters. (For example, there are
exactly 7227 \TeX\ points in 254 centimeters, and \TeX82 works with scaled
points where there are $2^{16}$ sp in a point, so \TeX82 sets |num=25400000|
and $|den|=7227\cdot2^{16}=473628672$.)
@^sp@>
The |mag| parameter is what \TeX82 calls \.{\\mag}, i.e., 1000 times the
desired magnification. The actual fraction by which dimensions are
multiplied is therefore $mn/1000d$. Note that if a \TeX\ source document
does not call for any `\.{true}' dimensions, and if you change it only by
specifying a different \.{\\mag} setting, the \.{DVI} file that \TeX\
creates will be completely unchanged except for the value of |mag| in the
preamble and postable.
Finally, |k| and |x| allow the \.{DVI} writer to include a comment, which is not
interpreted further. The length of comment |x| is |k|, where |0<=k<256|.
\vutex\ prints this comment out, which, in \TeX82 files, contains the date
and time of execution.
@d id_byte=2 {identifies the kind of \.{DVI} files described here}
@ Font definitions for a given font number |k| contain further parameters
$$\hbox{|c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.}$$
The four-byte value |c| is the check sum that \TeX\ (or whatever program
generated the \.{DVI} file) found in the \.{TFM} file for this font;
|c| should match the check sum of the font found by programs that read
this \.{DVI} file.
@^check sum@>
Parameter |s| contains a fixed-point scale factor that is applied to the
character widths in font |k|; font dimensions in \.{TFM} files and other
font files are relative to this quantity, which is always positive and
less than $2^{27}$. It is given in the same units as the other dimensions
of the \.{DVI} file. Parameter |d| is similar to |s|; it is the ``design
size,'' and it is given in \.{DVI} units that have not been corrected for
the magnification~|mag| found in the preamble. Thus, font |k| is to be
used at $|mag|\cdot s/1000d$ times its normal size.
The remaining part of a font definition gives the external name of the font,
which is an ASCII string of length |a+l|. The number |a| is the length
of the ``area'' or directory, and |l| is the length of the font name itself;
the standard local system font area is supposed to be used when |a=0|.
The |n| field contains the area in its first |a| bytes.
Font definitions must appear before the first use of a particular font number.
Once font |k| is defined, it must not be defined again; however, we
shall see below that font definitions appear in the postamble as well as
in the pages, so in this sense each font number is defined exactly twice,
if at all. Like |nop| commands and \\{xxx} commands, font definitions can
appear before the first |bop|, or between an |eop| and a |bop|.
@ The last page in a \.{DVI} file is followed by `|post|'; this command
introduces the postamble, which summarizes important facts that \TeX\ has
accumulated about the file, making it possible to print subsets of the data
with reasonable efficiency. \vutex\ does not make use of this capability,
however.
The postamble has the form
$$\vbox{\halign{\hbox{#\hfil}\cr
|post| |p[4]| |num[4]| |den[4]| |mag[4]| |l[4]| |u[4]| |s[2]| |t[2]|\cr
$\langle\,$font definitions$\,\rangle$\cr
|post_post| |q[4]| |i[1]| 223's$[{\G}4]$\cr}}$$
Here |p| is a pointer to the final |bop| in the file. The next three
parameters, |num|, |den|, and |mag|, are duplicates of the quantities that
appeared in the preamble.
Parameters |l| and |u| give respectively the height-plus-depth of the tallest
page and the width of the widest page, in the same units as other dimensions
of the file. These numbers might be used by a \.{DVI}-reading program to
position individual ``pages'' on large sheets of film or paper.
Unfortunately, since characters can be set outside of the page boundaries,
these numbers cannot be used to set limits on the page area.
Parameter |s| is the maximum stack depth (i.e., the largest excess of
|push| commands over |pop| commands) needed to process this file. Then
comes |t|, the total number of pages (|bop| commands) present.
The postamble continues with font definitions, which are any number of
\\{fnt\_def} commands as described above, possibly interspersed with |nop|
commands. Each font number that is used in the \.{DVI} file must be defined
exactly twice: Once before it is first selected by a \\{fnt} command, and once
in the postamble.
@ The last part of the postamble, following the |post_post| byte that
signifies the end of the font definitions, contains |q|, a pointer to the
|post| command that started the postamble. An identification byte, |i|,
comes next; this currently equals~2, as in the preamble.
The |i| byte is followed by four or more bytes that are all equal to
the decimal number 223 (i.e., @'337 in octal). \TeX\ puts out four to seven of
these trailing bytes, until the total length of the file is a multiple of
four bytes, since this works out best on machines that pack four bytes per
word; but any number of 223's is allowed, as long as there are at least four
of them. In effect, 223 is a sort of signature that is added at the very end.
@^Fuchs, David Raymond@>
@* Plan of attack.
A \.{DVI} to device program must deal with a large amount of data. The
\.{DVI} files in most applications tend to be quite large.
For this reason, one of the primary goals in the design of this program was
to reduce memory requirements as much as possible. After much consideration
and examination of several other \.{DVI} drivers, it was decided that operation
of this program would occur in three distinct phases---prescan, font loading,
and page drawing.
Every \.{DVI} file is prescanned. During this phase, information about font
usage is collected. A very simple interpreter simply notices the |bop| and
|eop| tokens (to allow a subset of the pages to be printed), all of the
|font_def| commands (the name and other information is stored), and the
|set_char| commands (certain counters are incremented). For every font that is
declared, space is allocated on the main |mem| array for a table of information
regarding that font. This table contains that information that is necessary as
determined from the \TeX\ font metric (\.{TFM}) files.
During the second phase, the fonts are given a priority rating and the \.{TFM}
information table is created. At this point, all the information about
font usage in the file is located in the |mem| array. Therefore, a
routine named |base_font| is called. This routine scans the declared fonts
and assesses them as either printable ASCII fonts or as unprintable fonts.
The printable fonts are given priority ratings based on the use of characters in
each font. The font with priority 0 is the |base_font| and the horizontal
raster resolution is based on spacings determined by that font.
The vertical raster resolution is determined by the arbitrary assignment of a
set number of lines to the printed page. Each character or word is then
assigned a line in the page according to its vertical coordinate.
The routine |load_tfm_file| creates a table for each font, declaring the width
of each character in \.{DVI} units. Then a companion table is created with a
raster unit width for each character.
During phase three, the \.{DVI} file is reread one page at a time, and the
characters are ``drawn'' onto a |page|. This is a character array which
will be output to the device when the page is completed. Because of the coarse
raster resolution of the device, several characters may be placed within the
same array space, but the priority rating is used to determine which character
shall appear there. Thus, a |priority| array is kept to determine the
priority of any character appearing on the |page|. Also, because of
differences in \TeX\ font spacing, blank spaces may appear in words if the
characters were placed exactly in their raster positions. Instead, a |word|
array positions a sequence of characters into adjacent raster positions and then
attempts to reset the sequence into the space allocated by \TeX\ in the
|page|. Such words are left-justified in the allocated space so that
large spaces (or, possibly, one space) will appear between words in the
|page|.
We note that, if a sequence of characters is set from a font with smaller
character sizes than the |base_font|, then the space allocated by \TeX\ for that
word will be smaller than the raster width required. Hence, words consisting of
characters from small fonts may be truncated.
\vutex\ output is in one of two modes --- compressed or pure \TeX. In the pure
\TeX\ mode, each page is printed as it was constructed in the array, i.e. each
word is left-justified in the space provided by the \.{DVI} commands. Again,
a number of spaces may appear between word, and the output can require a wide
palette. Sub/super script lines will appear on separate associated lines.
The compression mode will shrink inter-word spacing to one space, with the first
character on each line appearing in its proper \TeX\ position, and all
subsequent words are shifted to the left. Difficulties in associating sub/super
script lines with the proper word in the baseline has forced a vertical
compression of the associated lines into the baseline before the horizontal
compression takes place. This may lead to ambiguous text, particularly with
mathematical expressions, but it is recommended that such text be viewed in the
unaltered non-compressed mode.
The format of the |mem| array reflects these phases. It is organized as
a large array of 32-bit words. The first 256 words are used as font pointers.
They are initialized to zero at the beginning of the program. Then, whenever
font number |k| is defined, 262 words are allocated in the |mem| array for the
font, and |mem[k]| is set to point to the sixth word of the allocated space.
The first allocated word, or |mem[mem[k]-6]|, consists of two sixteen bit
integers. The least significant sixteen bits are the external or directory
size of the font. The high order sixteen bits contain a pointer to the |names|
array, which contains the name of the font.
The next word (|mem[mem[k]-5]|) contains the |font_scaled_size| followed by the
font design size. Then comes the |font_space|. The next word is |font_type|
which determines the output array to use, followed by |font_status| to indicate
if the font is a printable font or not.
Finally comes the tabular information for the individual characters. They each
use two words. The first word, |mem[mem[k]+2*f]|, contains the |tfm_width| of
the character in \.{DVI} units. The next contains the |use_count| for the first
pass, and the |raster_width| during the second pass.
During the first pass, the first word of the first character in the table
contains the checksum of the \.{TFM} file loaded from the \.{DVI} file.
Hopefully this will match the checksum in the \.{TFM} file.
The |names| array will contain all of the string information in the file. It
is used as the input buffer from the terminal, the storage area for font names,
temporary space for special's, etc. A `string' consists of a pointer into
this array. The end of every string will be the value 0 as a marker.
|next_names_free| indicates the end of the table.
@ The following macroes should make the program slightly more readable, as they
calculate the offsets, etc. automatically.
@d font_desc_size=262
@d hi(#)==(# div 65536)
@d lo(#)==(# mod 65536)
@d raster_round(#)==round((#)/resol) + hh_offset
@d line_round(#)==round((#)/vresol) + vv_offset
@d set_line(#)==
if line_for[#]<0 then
begin line_for[#] := next_line_free;
next_line_free:=next_line_free + page_width;
end
@d @!font_check_sum == mem[cur_fptr] {Temporary location for checksum}
@d @!font_scaled_size == mem[cur_fptr-5] {Scaled size for current font}
@d @!font_design_size == mem[cur_fptr-4] {Design size for current font}
@d @!font_space == mem[cur_fptr-3] {Size of a thinspace in the font}
@d @!font_type == mem[cur_fptr-2] {Code to determine type of font}
@d @!declared == 1
@d @!other == 2
@d @!mexten == 3
@d @!msymbol == 4
@d @!mitalic == 5
@d @!tty == 6
@d @!roman == 7
@d @!font_status == mem[cur_fptr-1] {Priority of the current font}
@d @!font_nsave == mem[cur_fptr-6] {For storing the name of the font}
@d @!font_name == hi(mem[cur_fptr-6]) {High sixteen bits for font name ptr}
@d @!directory_size == lo(mem[cur_fptr-6]) {Low sixteen bits for external
size pointer}
@d @!tfm_width == mem[cur_char_ptr] {\.{TFM} width of current character}
@d @!use_count == mem[cur_char_ptr+1] {Use count used mostly in phase two.}
@d @!raster_width == mem[cur_char_ptr+1] {character width in rasters, phase
three}
@d @!temp_ar(#) == mem[next_mem_free+#] {`array' used for temporary storage}
@ We also need to define some of these variables, and initialize them.
@<Glob...@>=
@!mem : array [0..max_mem_size] of integer ; {major memory array}
@!names : array [0..name_size] of 0..127 ; {string array}
@!cur_fptr, @!cur_char_ptr : integer ; {temporary pointers}
@!next_mem_free, @!next_names_free : integer; { used for allocation }
i,j : integer ; {plain old index variable}
@ @<Set init...@>=
for i := 0 to 255 do
mem[i] := 0 ; { no fonts defined yet.}
next_mem_free := 256 ; { next available position in mem array }
next_names_free := 1 ; { next available position in names array }
@* Input from binary files.
@<Types...@>=
@!eight_bits=0..255; {unsigned one-byte quantity}
@!byte_file=packed file of eight_bits; {files that contain binary data}
@!word_file=packed file of integer; {for pixel file words}
@ The program deals with two binary file variables: |dvi_file| is the main input
file that we are translating into symbolic form, and |tfm_file| is the current
\.{TFM} file from which character raster information is being read. The third
file is the final output file, a text file.
@<Glob...@>=
@!dvi_file:byte_file; {the stuff we are \.{DVI}typing}
@!tfm_file:byte_file; {a font metric file}
@!bit_file:text_file; {where the final output goes}
@^system dependencies@>
@ To prepare these files for input, we |reset| them. An extension of
\PASCAL\ is needed in the case of |tfm_file|, since we want to associate
it with external files whose names are specified dynamically (i.e., not
known at compile time). The following code assumes that `|reset(f,s)|'
does this, when |f| is a file variable and |s| is a string variable that
specifies the file name. If |eof(f)| is true immediately after
|reset(f,s)| has acted, we assume that no file named |s| is accessible.
@^system dependencies@>
@p procedure open_dvi_file; {prepares to read packed bytes in |dvi_file|}
begin reset(dvi_file);
cur_loc:=0;
procedure reopen_dvi_file; {reopens the |dvi_file| for the next scan}
begin reset(dvi_file);
cur_loc:=0;
procedure open_bit_file; {prepares final output for writing}
begin rewrite(bit_file);
bit_is_open := true ;
procedure open_tfm_file; {opens \.{TFM} file}
begin reset(tfm_file,cur_name);
eof_tfm := eof(tfm_file);
procedure open_input_text ; {prepares to read from general purpose input}
begin reset(gen_input, cur_name) ;
end ;
@ |cur_loc| and |cur_name| are global variables: |cur_loc| is the number of the
byte about to be read next from |dvi_file|, and |cur_name| is a string variable
that will be set to the current \.{TFM} file name before |open_tfm_file| is
called.
@<Glob...@>=
@!cur_loc:integer; {where we are about to look, in |dvi_file|}
@!cur_name:packed array[1..name_length] of char; {external name,
with no lower case letters}
@ We shall use another set of simple functions to read the next byte or
bytes from |dvi_file|. There are seven possibilities, each of which is
treated as a separate function in order to minimize the overhead for
subroutine calls.
@^system dependencies@>
@p function get_byte:integer; {returns the next byte, unsigned}
var b:eight_bits;
begin if eof(dvi_file) then get_byte:=0
else begin read(dvi_file,b); incr(cur_loc); get_byte:=b;
end;
function signed_byte:integer; {returns the next byte, signed}
var b:eight_bits;
begin read(dvi_file,b); incr(cur_loc);
if b<128 then signed_byte:=b @+ else signed_byte:=b-256;
function get_two_bytes:integer; {returns the next two bytes, unsigned}
var a,@!b:eight_bits;
begin read(dvi_file,a); read(dvi_file,b);
cur_loc:=cur_loc+2;
get_two_bytes:=a*256+b;
function signed_pair:integer; {returns the next two bytes, signed}
var a,@!b:eight_bits;
begin read(dvi_file,a); read(dvi_file,b);
cur_loc:=cur_loc+2;
if a<128 then signed_pair:=a*256+b
else signed_pair:=(a-256)*256+b;
function get_three_bytes:integer; {returns the next three bytes, unsigned}
var a,@!b,@!c:eight_bits;
begin read(dvi_file,a); read(dvi_file,b); read(dvi_file,c);
cur_loc:=cur_loc+3;
get_three_bytes:=(a*256+b)*256+c;
function signed_trio:integer; {returns the next three bytes, signed}
var a,@!b,@!c:eight_bits;
begin read(dvi_file,a); read(dvi_file,b); read(dvi_file,c);
cur_loc:=cur_loc+3;
if a<128 then signed_trio:=(a*256+b)*256+c
else signed_trio:=((a-256)*256+b)*256+c;
function signed_quad:integer; {returns the next four bytes, signed}
var a,@!b,@!c,@!d:eight_bits;
begin read(dvi_file,a); read(dvi_file,b); read(dvi_file,c); read(dvi_file,d);
cur_loc:=cur_loc+4;
if a<128 then signed_quad:=((a*256+b)*256+c)*256+d
else signed_quad:=(((a-256)*256+b)*256+c)*256+d;
@* Reading the font information.
\.{DVI} file format does not include information about character widths, since
that would tend to make the files a lot longer. But a program that reads a
\.{DVI} file is supposed to know the widths of the characters that appear in
\\{set\_char} commands. Therefore \vutex\ reads in a \.{TFM} file for each
font that is used.
@ It is, of course, a simple matter to print the name of a given font.
@p procedure print_font(@!f:integer);
var k:0..name_size; {index into |names|}
cur_fptr : integer ;
begin cur_fptr := mem[f]; if font_name=0 then print_ln('UNDEFINED!')
@.UNDEFINED@>
else begin k := font_name ;
if names[k]>0 then begin
while names[k]>0 do begin
print(xchr[names[k]]) ;
k := k + 1 ;
end ;
print(':');
end ;
k := k + 1 ;
while names[k]>0 do begin
print(xchr[names[k]]);
k := k + 1 ;
end ;
print_ln(' ');
end;
@ We need a function that will read in a word from the \.{TFM} file. If
the particular system
@^system dependencies@>
requires buffering, here is the place to do it. It also sets a global flag
|eof_tfm| when it reaches the end of the file. If this flag is set on
entrance to |load_tfm_file|, it is assumed that the file is bad.
@p function tfm_integer : integer ;
var i:integer;
begin read(tfm_file, i);
eof_tfm:=eof(tfm_file);
tfm_integer:=i;
@ There is nothing wrong with defining |eof_tfm| here.
@<Glob...@>=
@!eof_tfm:boolean; {true when end of \.{TFM} file is reached.}
@ The most important part of |load_tfm_file| is the conversion to \.{DVI} units,
which involves multiplying the relative values in the \.{TFM} file by the
scaling factor in the \.{DVI} file. This fixed-point multiplication
must be done with precisely the same accuracy by all \.{DVI}-reading programs,
in order to validate the assumptions made by \.{DVI}-writing programs
like \TeX82.
Let us therefore summarize what needs to be done. Each width in a \.{TFM}
file appears as a four-byte quantity called a |fix_word|. A |fix_word|
whose respective bytes are $(a,b,c,d)$ represents the number
$$x=\left\{\vcenter{\halign{$#$,\hfil\qquad&if $#$\hfil\cr
b\cdot2^{-4}+c\cdot2^{-12}+d\cdot2^{-20}&a=0;\cr
-16+b\cdot2^{-4}+c\cdot2^{-12}+d\cdot2^{-20}&a=255.\cr}}\right.$$
(No other choices of $a$ are allowed, since the magnitude of a \.{TFM}
dimension must be less than 16.) We want to multiply this quantity by the
integer~|z|, which is known to be less than $2^{27}$. Let $\alpha=16z$.
If $|z|<2^{23}$, the individual multiplications $b\cdot z$, $c\cdot z$,
$d\cdot z$ cannot overflow; otherwise we will divide |z| by 2, 4, 8, or
16, to obtain a multiplier less than $2^{23}$, and we can compensate for
this later. If |z| has thereby been replaced by $|z|^\prime=|z|/2^e$, let
$\beta=2^{4-e}$; we shall compute
$$\lfloor(b+c\cdot2^{-8}+d\cdot2^{-16})\,z^\prime/\beta\rfloor$$ if $a=0$,
or the same quantity minus $\alpha$ if $a=255$. This calculation must be
done exactly, for the reasons stated above; the following program does the
job in a system-independent way, assuming that arithmetic is exact on
numbers less than $2^{31}$ in magnitude.
@p function tfm_to_int(val:integer) : integer ;
var alpha, beta, x, z:integer ; {help in converting widths}
b0, b1, b2, b3:eight_bits ; {temporary byte storage}
begin z := font_scaled_size ;
@<Split tfm value up into bytes@>;
@<Replace |z| by $|z|^\prime$ and compute $\alpha,\beta$@>;
z:=(((((b3*z)div@'400)+(b2*z))div@'400)+(b1*z))div beta ;
if b0=255 then z := z - alpha ;
tfm_to_int := z ;
end ;
@ @<Split tfm value up...@>=
begin x:=val ;
if x>=0 then b0:=x div @'100000000
else begin x:=(x+@'10000000000)+@'10000000000; b0:=x div @'100000000+128;
end ;
x:=x mod @'100000000;
b1:=x div @'200000;x:=x mod @'200000;b2:=x div @'400;b3:=x mod @'400; end;
@ @<Replace |z|...@>=
begin alpha:=16*z; beta:=16;
while z>=@'40000000 do
begin z:=z div 2; beta:=beta div 2;
end;
end ;
@ And, finally, we have the procedure which will extract the information from
the \.{TFM} file. This routine is for input from the \.{TFM} file, which is (or
should be) a |packed file of integer|, so this should cause no grief. See the
documentation on |tftopl| for the structure of the file.
@ @p procedure load_tfm_file ; {read in the width data}
label 9997, {used for bad format}
9999; {used for normal exit}
var r:integer; {used for |cur_name| manipulation}
i, k : integer ; {used for general pointer}
bc, ec : integer ; {first and last characters in font}
@!ptr : integer; {pointer to width table}
begin
@<Move tfm name into the |cur_name| string@> ;
if out_mode > 0 then print_ln('Trying to load ',cur_name) ;
open_tfm_file ;
k := next_mem_free ;
if eof_tfm then goto 9997 ;
while not eof_tfm do begin
mem[k] := tfm_integer ;
k := k + 1 ;
if k > max_mem_size then
abort(clone,' memory size exceeded on load of tfm file!') ;
end ;
bc := hi(mem[next_mem_free+1]) ;
ec := lo(mem[next_mem_free+1]) ;
if mem[next_mem_free+6]<>font_check_sum then
print_ln('Checksum in tfm file does not match that in dvi file!') ;
k := next_mem_free + lo(mem[next_mem_free]) + 6 - bc ;
cur_char_ptr := cur_fptr ;
for i := 0 to bc-1 do begin
tfm_width := 0 ;
cur_char_ptr := cur_char_ptr + 2 ;
end ;
for i := bc to ec do begin
if mem[k+i] > 0 then
ptr := mem[k+i] div @'100000000
else
ptr := ((mem[k+i] + one_fourth) + one_fourth) div @'100000000 + 128 ;
tfm_width := tfm_to_int(mem[ptr + ec + k + 1 ]) ;
cur_char_ptr := cur_char_ptr + 2 ;
end ;
for i := ec+1 to 127 do begin
tfm_width := 0 ;
cur_char_ptr := cur_char_ptr + 2 ;
end ;
goto 9999 ;
9997: print_ln('---not loaded, TFM file is bad') ;
9999: end ;
@* User interface.
\vutex\ is normally considered a filter, and therefore operates very
quietly unless some error is encountered. However, for debugging purposes
or the inquisitive, one can access the variable |out_mode|. This variable
will allow the tracing of \vutex. It has two possible values: 0, and 1.
If it is zero, then the program is very quiet, only informing the user of
errors. If it is one, a trace is executed for debugging purposes.
There are a few optional parameters requested when this program is invoked. It
is possible to print only a restricted subset of the pages by specifying the
desired starting page and the maximum number of pages. You also may select the
number of rasters across a page and the compression mode for output.
The starting page is specified by giving a sequence of 1 to 10 numbers or
asterisks separated by dots. For example, the specification `\.{1.*.-5}'
can be used to refer to a page output by \TeX\ when $\.{\\count0}=1$
and $\.{\\count2}=-5$. (Recall that |bop| commands in a \.{DVI} file
are followed by ten `count' values.) An asterisk matches any number,
so the `\.*' in `\.{1.*.-5}' means that \.{\\count1} is ignored when
specifying the first page. If several pages match the given specification,
\vutex\ will begin with the earliest such page in the file. The
default specification `\.*' (which matches all pages) therefore denotes
the page at the beginning of the file.
When \vutex\ begins, it engages the user in a brief dialog so that the
options will be specified. This part of \vutex\ requires nonstandard \PASCAL\
constructions to handle the online interaction; so it may be preferable in some
cases to omit the dialog and simply to stick to the default options
(|out_mode=terse|, starting page `\.*', |max_pages=1000000|, |print_width=160|,
and |compress=false|). On other hand, the system-dependent routines that are
needed are not complicated, so it will not be terribly difficult to introduce
them. Normally, however, the necessary parameters should be taken from the
command line which invokes the program (if this is possible.) Then, this part
of the program would not even be used. This is in the domain of the change
file, however.
@^system dependencies@>
@d errors_only=0 {value of |out_mode| when minimal printing occurs}
@d terse=1 {value of |out_mode| for abbreviated output}
@<Glob...@>=
@!out_mode:errors_only..terse; {controls the amount of output}
@!max_pages:integer; {at most this many |bop..eop| pages will be printed}
@!page_width:integer; {page width for setting characters}
@!num_lines:integer; {number of lines in a page}
@!print_width: integer; {the number of raster units across screen before
truncation}
@!print_height: integer; {the number of lines printed on each screen}
@!compress: boolean; {true if interword spacing to be compressed to one blank}
@!batch_mode: boolean; {true if pages to be processed with no interaction}
@ The starting page specification is recorded in two global arrays called
|start_count| and |start_there|. For example, `\.{1.*.-5}' is represented
by |start_there[0]=true|, |start_count[0]=1|, |start_there[1]=false|,
|start_there[2]=true|, |start_count[2]=-5|.
We also set |start_vals=2|, to indicate that count 2 was the last one
mentioned. The other values of |start_count| and |start_there| are not
important, in this example.
@<Glob...@>=
@!start_count:array[0..9] of integer; {count values to select starting page}
@!start_there:array[0..9] of boolean; {is the |start_count| value relevant?}
@!start_vals:0..9; {the last count considered significant}
@!count:array[0..9] of integer; {the count values on the current page}
@ @<Set init...@>=
out_mode := errors_only;
max_pages:=1000000; start_vals:=0; start_there[0]:=false;
page_width:= dfl_p_width; num_lines:=dfl_n_lines;
print_width:= dfl_screen_width; print_height:= dfl_screen_height;
compress:=false;batch_mode:=false;
@ Here is a simple subroutine that tests if the current page might be the
starting page.
@p function start_match:boolean; {does |count| match the starting spec?}
var k:0..9; {loop index}
@!match:boolean; {does everything match so far?}
begin match:=true;
for k:=0 to start_vals do
if start_there[k]and(start_count[k]<>count[k]) then match:=false;
start_match:=match;
@ The |input_ln| routine waits for the user to type a line at his or her
terminal; then it puts ASCII-code equivalents for the characters on that line
into the |buffer| array. The |term_in| file is used for terminal input,
and |term_out| for terminal output.
@^system dependencies@>
@<Glob...@>=
@!buffer:array[0..terminal_line_length] of ASCII_code;
@!term_in:text_file; {the terminal, considered as an input file}
@!term_out:text_file; {the terminal, considered as an output file}
@ Since the terminal is being used for both input and output, some systems
need a special routine to make sure that the user can see a prompt message
before waiting for input based on that message. (Otherwise the message
may just be sitting in a hidden buffer somewhere, and the user will have
no idea what the program is waiting for.) We shall call a system-dependent
subroutine |update_terminal| in order to avoid this problem.
@^system dependencies@>
@d update_terminal == break(term_out) {empty the terminal output buffer}
@ During the dialog, \vutex\ will treat the first blank space in a
line as the end of that line. Therefore |input_ln| makes sure that there
is always at least one blank space in |buffer|.
@^system dependencies@>
@p procedure input_ln; {inputs a line from the terminal}
var k:0..terminal_line_length;
begin update_terminal; reset(term_in);
if eoln(term_in) then read_ln(term_in);
k:=0;
while (k<terminal_line_length)and not eoln(term_in) do
begin buffer[k]:=xord[term_in^]; incr(k); get(term_in);
end;
buffer[k]:=" ";
@ The global variable |buf_ptr| is used while scanning each line of input;
it points to the first unread character in |buffer|.
@<Glob...@>=
@!buf_ptr:0..terminal_line_length; {the number of characters read}
@ Here is a routine that scans a (possibly signed) integer and computes
the decimal value. If no decimal integer starts at |buf_ptr|, the
value 0 is returned. The integer should be less than $2^{31}$ in
absolute value.
@p function get_integer : integer;
var x:integer; {accumulates the value}
@!negative:boolean; {should the value be negated?}
begin if buffer[buf_ptr]="-" then
begin negative:=true; incr(buf_ptr);
end
else negative:=false;
x:=0;
while (buffer[buf_ptr]>="0")and(buffer[buf_ptr]<="9") do
begin x:=10*x+buffer[buf_ptr]-"0"; incr(buf_ptr);
end;
if negative then get_integer:=-x @+ else get_integer:=x;
@ The selected options are put into global variables by the |dialog|
procedure, which is called just as \vutex\ begins.
@^system dependencies@>
@p procedure dialog;
var k:integer; {loop variable}
dum_width:integer;
dum_height:integer;
begin rewrite(term_out); {prepare the terminal for output}
dum_width := 0; dum_height := 0;
@<Determine the desired interactive mode@>;
@<Determine the desired |start_count| values@>;
@<Determine the desired |max_pages|@>;
@<Determine the number of printed rasters |dum_width|@>;
@<Determine the number of printed rows |dum_height|@>;
@<Determine the compression mode@>;
@<Print all the selected options@>;
@<Print the page action options@>;
@<Adjust page dimensions@>;
@ @<Determine the desired interactive mode@>=
write(term_out,'Interactive mode (default=Y): ');
batch_mode := false; input_ln; buf_ptr:=0;
if (buffer[buf_ptr] = "N") or (buffer[buf_ptr] = "n") then batch_mode:=true ;
@ @<Determine the desired |start...@>=
write(term_out,'Starting page (default=*): ');
start_vals:=0; start_there[0]:=false;
input_ln; buf_ptr:=0; k:=0;
@<Parse the desired |start_vals|@>
@ These two routines have been split in half so it is easier to accept these
options from the command line if your operating system makes that possible.
Put a question mark at the end of the line to end it when doing it this way.
@<Parse the desired |start_vals|@>=
if (buffer[buf_ptr] <> "?") and (buffer[buf_ptr] <> " ") and
(buffer[buf_ptr] <> "/") then begin
repeat if buffer[buf_ptr]="*" then
begin start_there[k]:=false; incr(buf_ptr);
end
else begin start_there[k]:=true; start_count[k]:=get_integer;
end;
if (k<9)and(buffer[buf_ptr]=".") then
begin incr(k); incr(buf_ptr);
end
else if (buffer[buf_ptr]=" ") or (buffer[buf_ptr]="/") or
(buffer[buf_ptr]="?") then
start_vals:=k
else begin k := 0 ; start_vals := 0 ; end ;
until start_vals=k ;
@ @<Determine the desired |max_pages|@>=
write(term_out,'Maximum number of pages (default=1000000): ');
max_pages := 1000000; input_ln; buf_ptr:=0;
@<Parse the desired |max_pages|@>
@ @<Parse the desired |max_pages|@>=
begin max_pages:=get_integer;
if max_pages=0 then
max_pages := 1000000 ;
end
@ @<Determine the number of printed rasters |dum_width|@>=
write(term_out,'Width of output in characters',
' (default=160/batch, 80/interactive): ');
input_ln; buf_ptr:=0; dum_width:=get_integer
@ @<Determine the number of printed rows |dum_height|@>=
write(term_out,'Number of printed lines per screen',
' (default=280/batch, 22/interactive): ');
input_ln; buf_ptr:=0;
dum_height:=get_integer
@ @<Determine the compression mode@>=
write(term_out,'Compress interword spaces, Y or N (default=N): ');
input_ln; buf_ptr:=0; compress:=false ;
if (buffer[buf_ptr] = "Y") or (buffer[buf_ptr] = "y") then compress:=true ;
@ We may have to adjust the page dimensions to fit on the internal array. If
the compression mode has been chosen, we must allow extra room for the
uncompressed text.
@ @<Adjust page dimensions@>=
if dum_width=0 then
if batch_mode then print_width := page_width
else print_width := dfl_screen_width
else print_width := dum_width;
if compress and (print_width > (page_width * 3 div 4)) then
page_width := 4 * print_width div 3
else if (not compress) and (print_width > page_width - hh_offset) then
page_width := print_width + hh_offset;
if page_width > max_p_width then
begin
page_width := max_p_width;
print_width := page_width - hh_offset;
end;
num_lines := total_rast div page_width;
if dum_height=0 then
if batch_mode then print_height := num_lines
else print_height := dfl_screen_height
else print_height := dum_height;
if print_height > num_lines then print_height := num_lines
@ After the dialog is over, we print the options so that the user
can see what \vutex\ thought was specified. This is only done if the
|out_mode| is greater than 0, so we can run quietly usually.
@<Print all the selected options@>=
if out_mode > 0 then begin
print_ln('Options selected:');
@.Options selected@>
print(' Starting page = ');
for k:=0 to start_vals do
begin if start_there[k] then print(start_count[k]:1)
else print('*');
if k<start_vals then print('.')
else print_ln(' ');
end;
print_ln(' Maximum number of pages = ',max_pages:1);
print_ln(' Number of printed characters = ',print_width:1);
print_ln(' Number of printed lines = ',print_height:1);
@ If the user has selected interactive mode, then a summary of the the page
action options is printed for the user's information.
@<Print the page action options@>=
if not batch_mode then
begin print_ln(' ');
print_ln(' At the end of each printed screen, enter one of:');
print_ln(' L - left, R - right, U - up, P - new page, <cr> - down');
print_ln(' ');
end;
@ Another procedure that is included for debugging and monitoring purposes
is the |diagnostics| procedure. This procedure prints out the memory usage
of \vutex\. Again, this is only done if the |out_mode| is greater than
zero.
@p procedure diagnostics ;
begin
if out_mode > 0 then begin
print_ln('Number of pages processed: ', actual_page_count:1) ;
print_ln('String pool usage: ',next_names_free:1, ' out of ', name_size:1,
' bytes') ;end ;
end ;
@* Defining fonts.
In \vutex, a font definition should only be recognized while the scanning
is not in the postamble, and when the pass is one.
A global variable |in_postamble| is provided to tell whether we are
processing the postamble or not.
@<Glob...@>=
@!in_postamble:boolean; {are we reading the postamble?}
@ @<Set init...@>=
in_postamble:=false;
@ The following subroutine does the necessary things when a \\{fnt\_def}
command is being processed.
@p procedure define_font(@!f:integer); {|f| is an external font number}
@!p:integer; {length of the area/directory spec}
@!n:integer; {length of the font name proper}
@!c,@!q,@!d:integer; {check sum, scaled size, and design size}
@!k:0..name_size; {indices into |names|}
begin if not in_postamble then begin
if (f>255) then abort('Only font definitions 0..255 are valid in ',
clone) ;
@.vutex capacity exceeded...@>
if (mem[f]<>0) then abort('Font number ',f:1,' already defined!') ;
@.Font already defined@>
cur_fptr := next_mem_free+6 ;
mem[f]:=cur_fptr ;
next_mem_free := next_mem_free + font_desc_size ;
@<Read the font parameters into position for font |nf|, and
print the font name@>;
end ;
end ;
@ This routine takes the data from the \.{DVI} file about the current font,
and loads it into memory. It also initializes the |use_count| for each
character.
@<Read the font parameters into position for font |nf|...@>=
c:=signed_quad; font_check_sum:=c;@/
q:=signed_quad; font_scaled_size := q ;
d:=signed_quad; font_design_size := d ;
font_space:=d div 6;@/
p:=get_byte; n:=get_byte;
if next_names_free + p + n + 2 > name_size then
abort(clone,' capacity exceeded (name size=',name_size:1,')!');
@.vutex capacity exceeded...@>
font_nsave := next_names_free * 65536 ;
if out_mode > 0 then print('Font ',f:1,': ');
for k := 1 to p do begin
names[next_names_free] := get_byte ;
next_names_free := next_names_free + 1 ;
end ;
names[next_names_free] := 0 ;
next_names_free := next_names_free + 1 ;
for k := 1 to n do begin
names[next_names_free] := get_byte ;
next_names_free := next_names_free + 1 ;
end ;
names[next_names_free] := 0 ;
next_names_free := next_names_free + 1 ;
cur_char_ptr := cur_fptr ;
for k := 0 to 127 do begin {initialize use counts}
use_count := 0 ;
cur_char_ptr := cur_char_ptr + 2 ;
end ;
font_type := declared ;
if out_mode > 0 then print_font(f) ;
@ If |p=0|, i.e., if no font directory has been specified, \vutex\
is supposed to use the default font directory, which is a
system-dependent place where the standard font \.{TFM} files are kept.
@^system dependencies@>
@d tfm_directory_name=='TeXfonts:'
@d tfm_directory_name_length=9
@<Glob...@>=
@!tfm_directory:packed array[1..tfm_directory_name_length] of char;
@ We stick this initial value into the |names| array for later use.
@<Set init...@>=
tfm_directory:=tfm_directory_name;
@ The string |cur_name| is supposed to be set to the external name of the
\.{TFM} file for the current font.
@^system dependencies@>
@<Move tfm name into the |cur_name| string@>=
begin
for k:=1 to name_length do cur_name[k]:=' ';
r := 1 ; k := font_name ;
if names[k]=0 then
begin for i:=1 to tfm_directory_name_length do
cur_name[i]:=tfm_directory[i];
r:=tfm_directory_name_length+1;
end else begin
while names[k] > 0 do begin
cur_name[r] := xchr[names[k]] ;
r := r + 1 ; k := k + 1 ;
end ;
end ;
k := k + 1 ;
while names[k] > 0 do begin
cur_name[r] := xchr[names[k]] ;
r := r + 1 ; k := k + 1 ;
end ;
cur_name[r] := '.' ; r := r + 1 ;
cur_name[r] := 'T' ; r := r + 1 ;
cur_name[r] := 'F' ; r := r + 1 ;
cur_name[r] := 'M' ; r := r + 1 ;
for k := 1 to r do
if (cur_name[k]>='a')and(cur_name[k]<='z') then
cur_name[k]:=xchr[xord[cur_name[k]]-@'40] ;
end ;
@* Special execution.
In this implementation, special commands are ignored. However, the structure to
handle such remains as a skeleton if some special procedures are desired.
We need a procedure that will handle any special commands that might appear.
Commands must have the following format:
\medskip
\line{\hskip20pt command(parameters$\ldots$)\hfil}
\medskip
If there is more than one command in a special, they must be separated by
spaces. The parameters are also separated by spaces. The string that was
contained in the special command is in the names array starting at
|next_names_free| and ending with a 0. Also, the |prescan| boolean should be
checked to see if this is the first or second time that this string was
encountered.
@p procedure do_special ;
begin
end ;
@ This is where the special commands are interpreted. We accumulate the
string, and pass a pointer to it to a routine called |do_special|. Note that
this routine is called twice, once for the prescan, and once for the actual
processing. This way, special commands that set up the device for a particular
mode can be included and processed during the second pass. During pass one,
specials on pages before the first page to be processed are also passed, as
they might contain configuration information.
@<Translate an |xxx| command@>=
begin
bad_char:=false;
if p+next_names_free > name_size then
abort('Out of string space during special!') ;
@.Out of string space during special@>
for k:=1 to p do
begin q:=get_byte;
if (q<" ")or(q>"~") then
bad_char:=true;
end;
if bad_char then print_ln('non-ASCII character in xxx command!');
@.non-ASCII character...@>
do_special ;
end ;
@* Interpreting the page commands.
The main work of \vutex\ is accomplished by the |do_page| procedure,
which produces the output for an entire page, assuming that the |bop|
command for that page has already been processed. This procedure is
essentially an interpretive routine that reads and acts on the \.{DVI}
commands. Of course, this is for the second pass. A |prescan| routine
is still necessary to load the font information.
@ The definition of \.{DVI} files refers to six registers,
$(h,v,w,x,y,z)$, which hold integer values in \.{DVI} units. In practice,
we also need registers |hh| and |vv|, the raster analogs of $h$ and $v$,
since it is not always true that |hh=raster_round(h)| or
|vv=line_round(v)|.
The stack of $(h,v,w,x,y,z)$ values is represented by eight arrays
called |hstack|, \dots, |zstack|, |hhstack|, and |vvstack|.
@<Glob...@>=
@!h,@!v,@!w,@!x,@!y,@!z,@!hh,@!vv:integer; {current state values}
@!hstack,@!vstack,@!wstack,@!xstack,@!ystack,@!zstack:
array [0..stack_size] of integer; {pushed down values in \.{DVI} units}
@!hhstack,@!vvstack:
array [0..stack_size] of integer; {pushed down values in rasters}
@ Three characteristics of the pages (their |max_v|, |max_h|, and
|max_s|) are specified in the postamble, and a warning message
is printed if these limits are exceeded. Actually |max_v| is set to
the maximum height plus depth of a page, and |max_h| to the maximum width,
for purposes of page layout. Since characters can legally be set outside
of the page boundaries, it is not an error when |max_v| or |max_h| is
exceeded. But |max_s| should not be exceeded.
The postamble also specifies the total number of pages; \vutex\
checks to see if this total is accurate.
@<Glob...@>=
@!max_v:integer; {the value of |abs(v)| should probably not exceed this}
@!max_h:integer; {the value of |abs(h)| should probably not exceed this}
@!max_s:integer; {the stack depth should not exceed this}
@!max_v_so_far,@!max_h_so_far,@!max_s_so_far:integer; {the record high levels}
@!page_count:integer; {the total number of pages seen so far}
@!hpos_ed, @!vpos_ed : boolean ; {are we positioned in this respect?}
@ @<Set init...@>=
max_v:=@'17777777777-99; max_h:=@'17777777777-99; max_s:=stack_size+1;@/
max_v_so_far:=0; max_h_so_far:=0; max_s_so_far:=0; page_count:=0;
@ Before we get into the details of |do_page|, it is convenient to
consider a simpler routine that computes the first parameter of each
opcode.
@d four_cases(#)==#,#+1,#+2,#+3
@d eight_cases(#)==four_cases(#),four_cases(#+4)
@d sixteen_cases(#)==eight_cases(#),eight_cases(#+8)
@d thirty_two_cases(#)==sixteen_cases(#),sixteen_cases(#+16)
@d sixty_four_cases(#)==thirty_two_cases(#),thirty_two_cases(#+32)
@p function first_par(o:eight_bits):integer;
begin case o of
sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64):
abort('Can''t call first_par like this.') ;
set1,put1,fnt1,xxx1,fnt_def1: first_par:=get_byte;
set1+1,put1+1,fnt1+1,xxx1+1,fnt_def1+1: first_par:=get_two_bytes;
set1+2,put1+2,fnt1+2,xxx1+2,fnt_def1+2: first_par:=get_three_bytes;
right1,w1,x1,down1,y1,z1: first_par:=signed_byte;
right1+1,w1+1,x1+1,down1+1,y1+1,z1+1: first_par:=signed_pair;
right1+2,w1+2,x1+2,down1+2,y1+2,z1+2: first_par:=signed_trio;
set1+3,set_rule,put1+3,put_rule,right1+3,w1+3,x1+3,down1+3,y1+3,z1+3,
fnt1+3,xxx1+3,fnt_def1+3: first_par:=signed_quad;
nop,bop,eop,push,pop,pre,post,post_post,undefined_commands: first_par:=0;
w0: first_par:=w;
x0: first_par:=x;
y0: first_par:=y;
z0: first_par:=z;
sixty_four_cases(fnt_num_0): first_par:=o-fnt_num_0;
@ We need a routine to skip over a font definition. This is very simple,
just requires skipping over the parameters:
@p procedure skip_font_def ;
var @!dumq, @!dumi, i : integer ;
begin
dumq := signed_quad ; dumq := signed_quad ; dumq := signed_quad ;
dumi := get_byte + get_byte ;
for i := 1 to dumi do dumi := get_byte ;
end ;
@ A useful constant:
@<Constants...@>=
one_fourth = 1073741824 ;
@ Strictly speaking, the |do_page| procedure is really a function with
side effects, not a `\&{procedure}'; it returns the value |false| if
\vutex\ should be aborted because of some unusual happening. The
subroutine is organized as a typical interpreter, with a multiway branch
on the command code followed by |goto| statements leading to routines that
finish up the activities common to different commands. We will use the
following labels:
@d fin_set=41 {label for commands that set or put a character}
@d fin_rule=42 {label for commands that set or put a rule}
@d move_right=43 {label for commands that change |h|}
@d move_down=44 {label for commands that change |v|}
@d show_state=45 {label for commands that change |s|}
@d change_font=46 {label for commands that change |cur_font|}
@ Now we have the actual routine that draws a character. It simply sets the
character pointer and calls the routine to set the character in the page.
@p procedure draw_char(p:integer) ;
@<Character drawing procedures@>
begin cur_char_ptr := cur_fptr + 2*p ;
dev_char_draw ;
hpos_ed := false ;
end ;
@ Simple horizontal or vertical rules can be created from underscores and
vertical bars. They are given low priority and will be replaced by all other
characters. If a vertical line segment is the only character to appear on a line
in the page, then that line will not be printed.
@p procedure dev_rule_draw(rh, rw: integer) ;
label done ;
var position : integer ;
i,j : integer ;
begin if line_for[vv] < 0 then begin
line_for[vv] := next_line_free ;
next_line_free := next_line_free + page_width ;
end;
position := line_for[vv] ; j := position + hh + 1 ;
if j < position + page_width then
if rw >= rh then
begin for i := 1 to rw do
begin if (page[j] = xchr[32]) and (j > position) then
page[j] := xchr[95]
else if page[position] = xchr[32] then page[position] := xchr[62] ;
incr(j) ;
if (j >= position + page_width) then
begin if page[position] = xchr[32] then
page[position] := xchr[62] ;
goto done ;
end
end ;
end else if j > position then
begin for i := 1 to rh do
begin if page[j] = xchr[32] then
page[j] := xchr[124]
else page[j-hh-1] := xchr[62] ;
if line_for[vv-i] < 0 then
begin line_for[vv-i] := next_line_free ;
next_line_free := next_line_free + page_width ;
end ;
position := line_for[vv-i] ;
j := position + hh + 1 ;
if j < 0 then goto done ;
end ;
end ;
done: end ;
@ On entrance to this routine, the character to be drawn will be in |p|, the
font in |cur_fptr|. The routine should be responsible for insuring that the
device is set to the correct location on the page (|hh| and |vv|) and that the
current font is selected. The characters are drawn sequentially into a word
array.
@<Character drawing procedures@>=
procedure dev_char_draw ;
var position : integer ; {points to beginning of raster row}
i : integer ;
c : char ; {the ASCII character to be set}
begin if not set_word then @<Initialize word@> ;
incr(in_word) ; position := line_for[word_vv] ;
hpos_ed := true ; vpos_ed := true ;
if (hh >= 0 ) then
if (word_hh+in_word < page_width) then
begin i := position+ word_hh + in_word ;
if (i < total_rast) then
if (ord(priority[i]) > font_status) and
(ord(word_priority[in_word]) > font_status) then
begin case font_type of
roman: @<Cases for the roman fonts@> ;
tty: c := xchr[p];
mitalic: c := mchr[p];
msymbol: c := schr[p];
mexten: c := echr[p];
othercases c := dfl_chr
endcases ;
word[in_word] := c ;
word_priority[in_word] := xchr[font_status] ;
end
else if page[position] = xchr[32] then
page[position] := xchr[62]
else if page[position] = xchr[32] then
page[position] := xchr[62] ;
end else if page[position] = xchr[32] then
page[position] := xchr[62]
else if page[position] = xchr[32] then
page[position] := xchr[62] ;
if font_status > 0 then
if (page[position] = xchr[32]) or (page[position]=xchr[62]) then
page[position] := xchr[42];
end ;
@ Variables must be initialized at the beginning of a new word. If the word is
appearing on a new line, the first space in the priority array is used to flag
the line as a baseline or an associated sub/super script line.
@<Initialize word@>=
begin set_word := true ;
word_hh := hh; word_vv := vv ;
in_word := 0;
if line_for[vv] < 0 then
begin line_for[vv] := next_line_free ;
next_line_free := next_line_free + page_width ;
end ;
if prev_vv >= 0 then
begin if (priority[line_for[vv]]=xchr[126]) then
if prev_vv=vv-1 then
begin page[line_for[vv]] := '-' ;
priority[line_for[vv]] := '-' ;
end
else if prev_vv=vv+1 then
begin page[line_for[vv]] := '+' ;
priority[line_for[vv]] := '+' ;
end
else priority[line_for[vv]] := 'b'
end
else priority[line_for[vv]] := 'b' ;
prev_vv := vv ;
for i := 1 to page_width do
begin word[i] := ' ' ;
word_priority[i] := xchr[126] ;
end ;
@ The layout for the standard roman fonts is slightly different from the ASCII
table.
@d ff == @'13 {these define the ligatures in roman fonts}
@d fi == @'14
@d fl == @'15
@d ffi == @'16
@d ffl == @'17
@d ss == @'31
@d ae == @'32
@d oe == @'33
@d AE == @'35
@d OE == @'36
@<Cases for the roman fonts@>=
case p of
ff: begin word[in_word] := rchr[102] ;
word_priority[in_word] := xchr[font_status] ;
incr(in_word) ; c := rchr[102] ; end ;
fi: begin word[in_word] := rchr[102] ;
word_priority[in_word] := xchr[font_status] ;
incr(in_word) ; c := rchr[105] ; end ;
fl: begin word[in_word] := rchr[102] ;
word_priority[in_word] := xchr[font_status] ;
incr(in_word) ; c := rchr[108] ; end ;
ffi: begin word[in_word] := rchr[102] ;
word_priority[in_word] := xchr[font_status] ;
incr(in_word) ; word[in_word] := rchr[102] ;
word_priority[in_word] := xchr[font_status] ;
incr(in_word) ; c := rchr[105] ; end ;
ffl: begin word[in_word] := rchr[102] ;
word_priority[in_word] := xchr[font_status] ;
incr(in_word) ; word[in_word] := rchr[102] ;
word_priority[in_word] := xchr[font_status] ;
incr(in_word) ; c := rchr[108] ; end ;
ss: begin word[in_word] := rchr[115] ;
word_priority[in_word] := xchr[font_status] ;
incr(in_word) ; c := rchr[115] ; end ;
ae: begin word[in_word] := rchr[97] ;
word_priority[in_word] := xchr[font_status] ;
incr(in_word) ; c := rchr[101] ; end ;
oe: begin word[in_word] := rchr[111] ;
word_priority[in_word] := xchr[font_status] ;
incr(in_word) ; c := rchr[101] ; end ;
AE: begin word[in_word] := rchr[65] ;
word_priority[in_word] := xchr[font_status] ;
incr(in_word) ; c := rchr[69] ; end ;
OE: begin word[in_word] := rchr[79] ;
word_priority[in_word] := xchr[font_status] ;
incr(in_word) ; c := rchr[69] ; end ;
othercases c:= rchr[p]
endcases
@ We need the global page arrays
@<Glob...@>=
@!page : array [0..total_rast] of text_char ; {the page of data}
@!priority : array [0..total_rast] of text_char ; {priority array}
@!line_for : array [1..dfl_n_lines] of integer ; {points to line in array}
@!next_line_free : integer ; {points to the next free position in array}
@!word : array [0..max_p_width] of text_char ; {string to hold complete word}
@!word_priority : array [0..max_p_width] of text_char ; {priority of each char}
@!in_word : integer ; {points to location in word}
@!set_word : boolean ; {set to true if a word has begun}
@!word_hh : integer ; {hh value where word began}
@!word_vv : integer ; {vv value of first character in word}
@!min_hh,@!min_vv : integer ; {minimum hh, vv values on the page}
@!prev_vv: integer ;
@ This routine will reset the characters in a word if the positioning has
left holes or missing characters.
@p procedure reset_word ;
var last:integer; {position of last character in word to be reset}
i,j,position : integer ;
begin set_line(vv) ;
if word_hh < min_hh then min_hh := max(0,word_hh) ;
if word_vv < min_vv then min_vv := max(0,word_vv) ;
if hh > word_hh then
begin position := line_for[word_vv] ;
j := position + word_hh + 1 ;
last := hh - word_hh + 1 ; if last > in_word then last := in_word ;
for i := 1 to last do
begin if j > position then
if priority[j] > word_priority[i] then
begin page[j] := word[i] ;
priority[j] := word_priority[i] ;
end
else if page[position] = xchr[32] then
page[position] := xchr[62]
else if page[position] = xchr[32] then
page[position] := xchr[62] ;
incr(j) ;
end ;
end ;
set_word := false ;
end ;
@ This routine is called at the beginning of each page, before anything
is processed.
@p procedure start_page ;
var i : integer ;
begin
hpos_ed := false ; vpos_ed := false ;
for i := 0 to total_rast do
begin page[i] := xchr[32];
priority[i] := xchr[126] ;
end;
for i := 1 to num_lines do line_for[i] := -1;
next_line_free := 0;
min_hh :=hh_offset ; min_vv := vv_offset ;
prev_vv := -1 ;
end ;
@ These constants define the key strokes for interactive paging action.
@<Constants...@>=
@!u_left = "L"; l_left="l"; {key for move left on page}
@!u_right= "R"; l_right="r"; {key for move right on page}
@!u_up= "U"; l_up="u"; {key for move up on page}
@!u_page= "P"; l_page="p"; {key for move to next page}
@!u_comp= "C"; l_comp="c"; {key for compress text on page}
@ When this routine is called, a full page has been processed, and the
page array is sent to the output device.
@p procedure finish_page ;
label done;
var i,j,k: integer ; {index counters}
last_hh: integer ; {raster column of furthest right character}
last_vv: integer ; {raster row of furthest down character}
first_p_hh: integer ; {raster column of first printed character}
last_p_hh: integer ; {raster column of last printed character}
first_p_row: integer ; {row number of first printed row}
last_p_row: integer ; {row number of last printed row}
rows_printed: integer ;
raster: integer ;
jspace: integer ;
shift: integer ;
next_row: integer ;
position: integer ;
average: integer; {average spacing between baselines}
menu : integer ; {used to interactively select page-action}
printing_page : boolean ;
begin first_p_hh := min_hh ; first_p_row := min_vv ;
last_hh := raster_round(max_h_so_far) + 1 ;
last_vv := line_round(max_v_so_far) ;
if last_hh >= page_width then
last_hh := page_width - 1 ;
@<Determine average spacing between nonempty lines@> ;
if compress then @<Compress spaces between words@> ;
printing_page := true;
position := first_p_row;
while (printing_page) do
begin last_p_hh := first_p_hh + print_width-2;
if last_p_hh >= page_width then last_p_hh := page_width;
write_ln(bit_file);
i := first_p_row;
rows_printed := 0;
while (i <= last_vv) and (rows_printed < print_height) do
begin if line_for[i] >= 0 then
begin position := i ;
raster := line_for[i] ;
if raster > total_rast-last_hh-1 then goto done ;
write(bit_file,page[raster]) ;
k := raster + last_p_hh ;
while page[k] = xchr[32] do decr(k) ;
k := k - raster - 1 ;
raster := raster + first_p_hh ;
for j := first_p_hh to k do
begin write(bit_file,page[raster]) ;
incr(raster) ;
end ;
write_ln(bit_file,page[raster]) ;
incr(rows_printed) ;
end
else if (average >0) and (i-position > average) then
begin write_ln(bit_file) ;
position := i ; incr(rows_printed) ;
end ;
incr(i);
end;
done: @<Determine page action@> ;
end;
end ;
@ Since each physical line is represented by perhaps several lines in the page
arrays, we try to determine the spacing between physical lines by taking the
average number of blank lines in the array between nonempty lines. This is used
to approximate the number of blank lines in a vertical shift in \TeX.
Note that a page number at the bottom of a sparse page may distort the average.
Hence if the last line is set apart substantially, we will ignore it.
@<Determine average spacing between nonempty lines@>=
begin
average := 0 ; k:= 0 ;
i := 1 ; j := line_for[i] ;
while (i <= last_vv) and ((j < 0) or (priority[j] <>'b')) do
begin incr(i); j:= line_for[i];
end;
position := i ;
incr(i) ;
while i <= last_vv do
begin j := line_for[i] ;
if (j>=0) then
if (priority[j]='b') then
begin shift := i - position ;
average := average + shift ;
incr(k) ; position := i ;
end;
incr(i);
end ;
if k > 1 then
begin if shift > 1.1*round(average*1.0/k) then
begin k := k - 1 ;
average := average - shift ;
end;
if k > 1 then
average := round(average*1.0 / k )
end;
@ If the compress option is chosen, the text is compressed vertically so that
associated sub/super script lines are pushed into the baseline. Then the
horizontal spacing is abandonned and interword spacing is compressed to one
blank space.
@<Compress spaces between words@>=
begin
for i := min_vv to last_vv do
if line_for[i]>=0 then
if priority[line_for[i]]='+' then begin
raster := line_for[i] ;
position := line_for[i+1] ;
for j := min_hh to last_hh do
if (page[raster+j]<>xchr[32]) then
if (page[position+j]=xchr[32]) or
(priority[raster+j]<priority[position+j]) then
begin
page[position+j] := page[raster+j] ;
priority[position+j] := priority[raster+j];
end;
line_for[i] := -2
end ;
for i := last_vv downto min_vv do
if line_for[i]>=0 then
if priority[line_for[i]]='-' then begin
raster := line_for[i] ;
position := line_for[i-1] ;
for j := min_hh to last_hh do
if (page[raster+j]<>xchr[32]) then
if (page[position+j]=xchr[32]) or
(priority[raster+j]<priority[position+j]) then
begin
page[position+j] := page[raster+j] ;
priority[position+j] := priority[raster+j];
end;
line_for[i] := -2
end ;
for i := min_vv to last_vv do
if line_for[i]>=0 then
begin raster := line_for[i] + 1 ;
next_row := raster + page_width - 1;
while (page[raster] = ' ') and (raster < next_row) do
incr(raster) ;
while (page[raster] <> ' ') and (raster < next_row) do
incr(raster) ;
jspace := raster ;
while raster < next_row do
begin
while (page[raster] = ' ') and (raster < next_row) do
incr(raster) ;
shift := raster - jspace - 1 ;
while (page[raster] <> ' ') and (raster < next_row) do
begin page[raster-shift] := page[raster] ;
incr(raster) ;
end ;
for k := raster - shift to raster - 1 do page[k] := ' ' ;
jspace := raster - shift ;
end ;
end ;
last_hh := min(last_hh,jspace);
@ When the portion of the page is printed, either the interactive user will be
given a menu of options to chose from or the batch job will continue.
@d null = 0
@d left_arrow = 1
@d right_arrow = 2
@d up_arrow = 3
@d down_arrow = 4
@d comp = 5
@d next_page = 6
@<Determine page action@> =
begin if batch_mode then
@<Draw graded horizontal rule@>
else begin last_p_row := i-1 ;
@<Select from menu@>;
case menu of
left_arrow: begin first_p_hh := first_p_hh - print_width + hor_overlap;
if first_p_hh < min_hh then first_p_hh := min_hh;
end;
right_arrow: begin first_p_hh := last_p_hh - hor_overlap;
if first_p_hh > last_hh - print_width then
first_p_hh := last_hh - print_width ;
end;
up_arrow: begin k := 0;
while (k < print_height-vert_overlap) and
(first_p_row > min_vv) do
begin first_p_row := first_p_row - 1 ;
j := line_for[first_p_row] ;
if (j >= 0) and (priority[j] ='b') then incr(k) ;
end;
end;
down_arrow: if last_p_row = last_vv then
printing_page := false
else
begin first_p_row := last_p_row - vert_overlap;
if first_p_row > last_vv - print_height then
first_p_row := last_vv - print_height + 1;
end;
comp: if (not compress) then @<Compress spaces between words@>;
next_page: @<Draw graded horizontal rule@>;
othercases @<Draw graded horizontal rule@>
endcases
end;
@ The interactive user will be given a choice of re-drawing the page, shifted
right, left, up, or down, or in the compressed mode. Any other selection will
cause the process to go to the next page.
@ @<Select from menu@> =
begin rewrite(term_out); write(term_out,'Hit key for page action.. ');
input_ln;
buf_ptr :=0;
menu := null;
if (buffer[buf_ptr] = u_left) or (buffer[buf_ptr] = l_left) then
menu := left_arrow
else if (buffer[buf_ptr] = u_right) or (buffer[buf_ptr] = l_right) then
menu := right_arrow
else if (buffer[buf_ptr] = u_up) or (buffer[buf_ptr] = l_up) then
menu := up_arrow
else if (buffer[buf_ptr] = u_page) or (buffer[buf_ptr] = l_page) then
menu := next_page
else if (buffer[buf_ptr] = u_comp) or (buffer[buf_ptr] = l_comp) then
menu := comp
else menu := down_arrow;
incr(buf_ptr);
@ At the end of each page, a graded horizontal rule will be drawn. The
gradations will be every 2 cm. measured from point with dvi value 0.
@<Draw graded horizontal rule@> =
begin write_ln(bit_file);
position := trunc(200000.0/num*den/resol) ;
write(bit_file,' _ cm --> ');
i := 0;
while i <= last_p_hh do
begin
if i > first_p_hh + 9 then
if i mod position = 0 then
begin
if 2*(i div position) < 10 then
write(bit_file,(2*(i div position)):1,'_')
else
write(bit_file,(2*(i div position)):2);
incr (i);
end
else write(bit_file,'_') ;
incr (i);
end;
write_ln(bit_file);
printing_page := false;
@ Some \PASCAL\ compilers severely restrict the length of procedure bodies,
so we shall split |do_page| into two parts, one of which is
called |spcl_cases|. The different parts communicate with each other
via the global variables mentioned above, together with the following ones:
@<Glob...@>=
@!s:integer; {current stack size}
@!cur_font:integer; {current internal font number}
@!a:integer; {byte number of the current command}
@ Here is the overall setup.
@p @t\4@>@<Declare the function called |spcl_cases|@>@;
function do_page:boolean;
label fin_set,fin_rule,move_right,show_state,done,9998,9999;
var o:eight_bits; {operation code of the current command}
@!p,@!q:integer; {parameters of the current command}
@!hhh:integer; {|h|, rounded to the nearest pixel}
begin start_page; cur_font:=0; {set current font undefined}
while mem[cur_font]>0 do cur_font := cur_font + 1 ;
s:=0; h:=0; v:=0; w:=0; x:=0; y:=0; z:=0;
hh:=raster_round(0); vv:=line_round(0);
{initialize the state variables}
cur_fptr := 0 ; set_word := false ;
while true do @<Translate the next command in the \.{DVI} file;
|goto 9999| with |do_page=true| if it was |eop|;
|goto 9998| if premature termination is needed@>;
9998: print_ln('!'); do_page:=false;
9999: end;
@ @<Translate the next command...@>=
begin a:=cur_loc ;
o:=get_byte; if o > 127 then p:=first_par(o);
if eof(dvi_file) then bad_dvi('the file ended prematurely');
@.the file ended prematurely@>
@<Start translation of command |o| and |goto| the appropriate label to
finish the job@>;
fin_set: @<Finish a command that either sets or puts a character, then
|goto move_right| or |done|@>;
fin_rule: @<Finish a command that either sets or puts a rule, then
|goto move_right| or |done|@>;
move_right: @<Finish a command that sets |h:=h+q|, then |goto done|@>;
show_state: @<Show the values of |h|, |v|, |w|, |x|, |y|, |z|,
|hh|, and |vv|; then |goto done|@>;
done:end
@ The multiway switch in |first_par|, above, was organized by the length
of each command; the one in |do_page| is organized by the semantics.
@<Start translation...@>=
if o<set_char_0+128 then draw_char(o)
else case o of
four_cases(set1), four_cases(put1): begin draw_char(o); goto fin_set;
end;
set_rule, put_rule: begin q:=signed_quad ; dev_rule_draw(round(p/vresol),
round(q/resol)); goto fin_rule;
end;
@t\4@>@<Cases for commands |nop|, |bop|, \dots, |pop|@>@;
@t\4@>@<Cases for horizontal motion@>@;
othercases if spcl_cases(o,p) then goto done@+else goto 9998
endcases ;
@ @<Declare the function called |spcl_cases|@>=
function spcl_cases(@!o:eight_bits;@!p:integer):boolean;
label change_font,move_down,done,9998;
var q:integer; {parameter of the current command}
@!k:integer; {loop index}
@!bad_char:boolean; {has a non-ASCII character code appeared in this \\{xxx}?}
@!pure:boolean; {is the command error-free?}
@!vvv:integer; {|v|, rounded to the nearest raster}
begin pure:=true;
case o of
@t\4@>@<Cases for vertical motion@>@;
@t\4@>@<Cases for fonts@>@;
four_cases(xxx1): begin @<Translate an |xxx| command@>; goto done; end;
pre: begin print_ln('preamble command within a page!'); goto 9998;
end;
@.preamble command within a page@>
post,post_post: begin print_ln('postamble command within a page!'); goto 9998;
@.postamble command within a page@>
end;
othercases begin print_ln('undefined command ',o:1,'!');
goto done;
@.undefined command@>
end
endcases;
move_down: @<Finish a command that sets |v:=v+p|, then |goto done|@>;
change_font: @<Finish a command that changes the current font,
then |goto done|@>;
9998: pure:=false;
done: spcl_cases:=pure;
@ @<Cases for commands |nop|, |bop|, \dots, |pop|@>=
nop: goto done;
bop: begin print_ln('bop occurred before eop'); goto 9998;
@.bop occurred before eop@>
end;
eop: begin finish_page ;
if s<>0 then print_ln('stack not empty at end of page (level ',
s:1,')!');
@.stack not empty...@>
do_page:=true; goto 9999;
end;
push: begin
if s=max_s_so_far then
begin max_s_so_far:=s+1;
if s=max_s then print_ln('deeper than claimed in postamble!');
@.deeper than claimed...@>
@.push deeper than claimed...@>
if s=stack_size then
begin print_ln(clone, ' capacity exceeded (stack size=',
stack_size:1,')'); goto 9998;
end;
end;
hstack[s]:=h; vstack[s]:=v; wstack[s]:=w;
xstack[s]:=x; ystack[s]:=y; zstack[s]:=z;
hhstack[s]:=hh; vvstack[s]:=vv; incr(s); goto show_state;
end;
pop: begin
if s=0 then print_ln('(illegal at level zero)!')
else begin if set_word then reset_word ;
decr(s);
hh:=hhstack[s]; vv:=vvstack[s];
h:=hstack[s]; v:=vstack[s]; w:=wstack[s];
x:=xstack[s]; y:=ystack[s]; z:=zstack[s];
end;
goto show_state;
end;
@ Our screen resolution is coarse, and we shall reset characters in words so
that the characters are left justified in the space \TeX\ provides. We shall do
our position calculations in the same manner that they would be done on the
printer. Rounding to the nearest raster is best done in the manner shown here,
so as to be inoffensive to the eye: When the horizontal motion is small, like a
kern, |hh| changes by rounding the kern; but when the motion is large, |hh|
changes by rounding the true position |h| so that accumulated rounding errors
disappear. We allow a larger space in the negative direction than in the
positive one, because \TeX\ makes comparatively large backspaces when it
positions accents.
Note that, if the horizontal movement is large, the word is reset.
@d out_space(#)==if cur_fptr=0 then hh := raster_round(h+p)
else begin
if (abs(p) >= round(vresol / 3)) and (set_word) then reset_word ;
if (p>=font_space)or(p<=-4*font_space) then
hh:=raster_round(h+p)
else hh:=hh+round(p/resol);
end;
q:=p; hpos_ed := false ; goto move_right
@<Cases for horizontal motion@>=
four_cases(right1):begin out_space('right',o-right1+1:1);
end;
w0,four_cases(w1):begin w:=p; out_space('w',o-w0:1);
end;
x0,four_cases(x1):begin x:=p; out_space('x',o-x0:1);
end;
@ Vertical motion is done similarly, but with the threshold between
``small'' and ``large'' increased by a factor of five. The idea is to make
fractions like ``$1\over2$'' round consistently, but to absorb accumulated
rounding errors in the baseline-skip moves.
Again, if the the vertical movement is large, the word is reset.
@d out_vmove(#)==if cur_fptr=0 then vv := line_round(v+p)
else begin
if (p <> 0) and (set_word) then reset_word ;
if abs(p)>=5*font_space then
vv:=line_round(v+p)
else vv:=vv+round(p/vresol);
end;
vpos_ed := false ;
goto move_down
@<Cases for vertical motion@>=
four_cases(down1):begin out_vmove('down',o-down1+1:1);
end;
y0,four_cases(y1):begin y:=p; out_vmove('y',o-y0:1);
end;
z0,four_cases(z1):begin z:=p; out_vmove('z',o-z0:1);
end;
@ @<Cases for fonts@>=
sixty_four_cases(fnt_num_0), four_cases(fnt1): goto change_font;
four_cases(fnt_def1): begin
skip_font_def;
goto done;
end;
@ @<Finish a command that either sets or puts a character...@>=
q:=tfm_width ;
if o>=put1 then goto done;
hh:=hh+raster_width;
goto move_right
@ @<Finish a command that either sets or puts a rule...@>=
if o=put_rule then goto done;
hh:=hh+round(q/resol); goto move_right
@ A sequence of consecutive rules, or consecutive characters in a fixed-width
font whose width is not an integer number of rasters, can cause |hh| to drift
far away from a correctly rounded value. \vutex\ ensures that the
amount of drift will never exceed |max_drift| rasters.
@d infinity==@'17777777777 {$\infty$ (approximately)}
@d max_drift=1 {we insist that abs|(hh-raster_round(h))<=max_drift|}
@<Finish a command that sets |h:=h+q|, then |goto done|@>=
hhh:=raster_round(h+q);
if abs(hhh-hh)>max_drift then begin hpos_ed := false ;
if hhh>hh then hh:=hhh-max_drift
else hh:=hhh+max_drift;
end ;
h:=h+q;
if abs(h)>max_h_so_far then
begin if abs(h)>max_h+99 then
begin print_ln('warning: |h|>',max_h:1,'!');
@.warning: |h|...@>
max_h:=abs(h);
end;
max_h_so_far:=abs(h);
end;
goto done
@ @<Finish a command that sets |v:=v+p|, then |goto done|@>=
if (v>0)and(p>0) then if v>infinity-p then
begin print_ln('arithmetic overflow! parameter changed from ',
@.arithmetic overflow...@>
p:1,' to ',infinity-v:1);
p:=infinity-v;
end;
if (v<0)and(p<0) then if -v>p+infinity then
begin print_ln('arithmetic overflow! parameter changed from ',
p:1, ' to ',(-v)-infinity:1);
p:=(-v)-infinity;
end;
vvv:=line_round(v+p);
if abs(vvv-vv)>max_drift then begin
if vvv>vv then vv:=vvv-max_drift
else vv:=vvv+max_drift;
end;
v:=v+p;
if abs(v)>max_v_so_far then
begin if abs(v)>max_v+99 then
begin print_ln('warning: |v|>',max_v:1,'!');
@.warning: |v|...@>
max_v:=abs(v);
end;
max_v_so_far:=abs(v);
end;
goto done
@ @<Show the values of |h|, |v|, |w|, |x|, |y|, |z|...@>=
goto done
@ @<Finish a command that changes the current font...@>=
cur_fptr := mem[p] ;
goto done
@* Skipping pages.
A routine that's much simpler than |do_page| is used to pass over
pages that are not being translated. The |skip_pages| subroutine
is assumed to begin just after the preamble has been read, or just
after a |bop| has been processed. It continues until either finding a
|bop| that matches the desired starting page specifications, or until
running into the postamble.
@p procedure skip_pages;
label 9999; {end of this subroutine}
var p:integer; {a parameter}
@!k:0..255; {command code}
@!down_the_drain:integer; {garbage}
bad_char:boolean ;
begin while true do
begin if eof(dvi_file) then bad_dvi('the file ended prematurely');
@.the file ended prematurely@>
k:=get_byte;
if k > 127 then begin p:=first_par(k);
case k of
bop: begin @<Pass a |bop| command, setting up the |count| array@>;
if not started and start_match then
begin started:=true; goto 9999;
end;
end;
set_rule,put_rule: down_the_drain:=signed_quad;
fnt_def1,fnt_def1+1,fnt_def1+2,fnt_def1+3: if prescan then begin
define_font(p);
end else skip_font_def ;
xxx1,xxx1+1,xxx1+2,xxx1+3: if prescan then begin
@<Translate an |xxx| command@>
end else
while p>0 do
begin down_the_drain:=get_byte; decr(p);
end;
post: begin in_postamble:=true; goto 9999;
end;
othercases do_nothing
endcases;
end ;
end;
9999:end;
@ Now we need a routine that will scan a page from the |bop| to the |eop|,
maintaining information about font usage. This routine also catches errors
like illegal font numbers or characters. It also counts the number of pages
actually processed.
@p function scan_page : boolean ;
label 9999; {end of this subroutine}
var p:integer; {a parameter}
@!k:0..255; {command code}
bad_char:boolean; {has a non-ASCII character code appeared in this \\{xxx}?}
@!down_the_drain:integer; {garbage}
begin actual_page_count := actual_page_count + 1 ;
while true do
begin a:=cur_loc ;
if eof(dvi_file) then bad_dvi('the file ended prematurely');
@.the file ended prematurely@>
k:=get_byte;
if eof(dvi_file) then bad_dvi('the file ended prematurely');
@.the file ended prematurely@>
if k<set_char_0+128 then begin
cur_char_ptr := cur_fptr + 2*k ;
incr(use_count);
end else begin
p:=first_par(k);
case k of
bop: begin @<Pass a |bop| command, setting up the |count| array@>;
if not started and start_match then
begin started:=true; goto 9999;
end;
end;
eop: goto 9999 ;
four_cases(set1),four_cases(put1): begin if p > 128 then
abort('character greater than 128 encountered!')
@.character greater than...@>
else begin cur_char_ptr := cur_fptr + 2*p ; incr(use_count); end; end ;
set_rule,put_rule: down_the_drain:=signed_quad;
fnt_def1,fnt_def1+1,fnt_def1+2,fnt_def1+3: define_font(p);
sixty_four_cases(fnt_num_0),four_cases(fnt1): begin
if p>255 then abort(clone, ' cannot handle font numbers > 255!') ;
@.vutex capacity exceeded...@>
cur_fptr := mem[p] ;
if cur_fptr=0 then abort('Undefined font!') ;
@.Undefined font@>
end ;
four_cases(xxx1): @<Translate an |xxx| command@>
post: begin in_postamble:=true; goto 9999;
end;
othercases do_nothing
endcases;
end ;
end;
9999:scan_page := true; end;
@ The global variable |started| indicates that we have found the
starting page. |last_page| points to the most recent page processed,
and |prev_page| points to the page before it. These two globals can be
used for processing a file in the reverse output, if the printer being
driven stacks the pages backwards, for instance.
@<Glob...@>=
@!started:boolean; {has the starting page been found?}
@!last_page: integer; {what is the most recent page processed?}
@!prev_page: integer; {what is the page before it?}
@!actual_page_count: integer; {how many pages to actually process}
@ @<Set init...@>=
started:=false;
last_page:=-1;
prev_page:=-1;
actual_page_count:=0;
@ @<Pass a |bop|...@>=
last_page:=cur_loc;
incr(page_count);
for k:=0 to 9 do count[k]:=signed_quad;
prev_page:=signed_quad ; {skip over the back pointers}
@ We need a procedure to open the output file, and to send out any
initialization that the printer might need.
@<Open bit file and initialize device@>=
open_bit_file ; init_device ;
@* Page scanning routines.
Here are a few miscellaneous routines which will scan the page, calling
the |pre_scan| and |do_page| routines as necessary.
@<Process the preamble@>=
open_dvi_file;
p:=get_byte; {fetch the first byte}
if p<>pre then bad_dvi('First byte isn''t start of preamble!');
@.First byte isn't...@>
p:=get_byte; {fetch the identification byte}
if p<>id_byte then
print_ln('identification in byte 1 should be ',id_byte:1,'!');
@.identification...should be n@>
@<Read the conversion units@>;
p:=get_byte; {fetch the length of the introductory comment}
print('''');
while p>0 do
begin decr(p); print(xchr[get_byte]);
end;
print_ln('''')
@ We also need a routine to skip over the preamble after the file has been
reset again.
@<Skip over the preamble@>=
reopen_dvi_file;
p:=get_byte; {fetch the first byte}
p:=get_byte; {fetch the identification byte}
p:=signed_quad; p:=signed_quad; p:=signed_quad; {skip over the mag stuff}
p:=get_byte; {fetch the length of the introductory comment}
while p>0 do begin
i:=get_byte; decr(p); end;
@ Read in the conversion units and magnification for the font.
@<Glob...@>=
@!num,den : integer; {numerator and denominator for conversion of dvi units}
@ @<Read the conversion units@>=
num:=signed_quad; if num<=0 then bad_dvi('numerator is ',num:1);
@.numerator is wrong@>
den:=signed_quad; if den<=0 then bad_dvi('denominator is ',den:1);
@.denominator is wrong@>
p:=signed_quad;
@ The code shown here uses a convention that has proved to be useful:
If the starting page was specified as, e.g., `\.{1.*.-5}', then
all page numbers in the file are displayed by showing the values of
counts 0, 1, and~2, separated by dots. Such numbers can, for example,
be displayed on the console of a printer when it is working on that
page.
@<Translate up to |max_pages| pages@>=
begin print('- Page ');
while max_pages>0 do
begin decr(max_pages);
for k:=0 to start_vals do
begin print('[',count[k]:1);
if k<start_vals then print('.')
else print ('] ');
end ;
if not do_page then bad_dvi('page ended unexpectedly');
@.page ended unexpectedly@>
begin
repeat k:=get_byte;
if (k>=fnt_def1)and(k<fnt_def1+4) then
begin p:=first_par(k); define_font(p); k:=nop;
end;
until k<>nop;
if k=post then
begin in_postamble:=true; goto done;
end;
if k<>bop then bad_dvi('byte ',cur_loc-1:1,' is not bop');
@.byte n is not bop@>
@<Pass a |bop|...@>;
end ;
end;
done: end;
@ This routine will prescan the file, using the |scan_page| macro to get
the information out of each page.
@<Prescan up to |max_pages| pages@>=
begin while max_pages>0 do
begin decr(max_pages);
if not scan_page then bad_dvi('page ended unexpectedly');
@.page ended unexpectedly@>
if in_postamble then goto pdone ;
repeat k:=get_byte;
if (k>=fnt_def1)and(k<fnt_def1+4) then
begin p:=first_par(k); skip_font_def; k:=nop;
end;
until k<>nop;
if k=post then
begin in_postamble:=true; goto pdone;
end;
if k<>bop then bad_dvi('byte ',cur_loc-1:1,' is not bop',k);
@.byte n is not bop@>
@<Pass a |bop|...@>;
end;
pdone:end
@* Font loading.
Now we have the procedure which determines the vertical and
horizontal raster resolution. On entrance to this procedure, all the font usage
information will have been loaded into the |use_count| entries for each font
descriptor. Based on this, |base_font| will determine the resolution to use
for assigning screen raster positions.
First, this routine reads the file \.{nonASCII.tex.fnt} to determine which fonts
are non ASCII, i.e. not printable on the screen. This list is shorter than the
list of ASCII fonts.
@p procedure base_font ;
var i, j, k : integer ;
f_type : integer ; {type for listed fonts}
lf,lh,bc,ec,nw,np : integer ; {help in decoding the \.{TFM} file}
value, x : integer ; {a \.{TFM} |fix_word|}
@<|base_font| variables@>
begin
@<Load non ASCII font information@> ;
@<Determine base font@> ;
end ;
@ The non ASCII font information is assumed to be in the following format:
\medskip
{\def\ff#1\cr{\line{\hskip\parindent\tt #1\hfil}}
\ff amsy 2\cr
\ff ambsy 2\cr
\ff amcsc 2\cr
\ff amit 4\cr
\ff amtt 5\cr
\medskip
The only item on a line is the font name and the type code.
@d font_list_file=='nonASCII.tex.fnt' {change this to correct name}
@d list_len==16
@<Glob...@>=
@!resol, vresol : real; {number of dvi units per raster or line}
@!gen_input : text_file ; {input path for text}
@!list_fonts : packed array [1..list_len] of char ; {non ASCII fonts name}
@!num_asc_fonts : integer ; {number of ASCII fonts declared}
@ @<Set init...@>=
list_fonts := font_list_file ;
@ @<Load non ASCII font information@>=
for k := 1 to name_length do cur_name[k] := ' ' ;
for k := 1 to list_len do
if (list_fonts[k] <= 'z') and (list_fonts[k] >= 'a') then
cur_name[k] := xchr[xord[list_fonts[k]]-@'40]
else cur_name[k] := list_fonts[k] ;
open_input_text ;
num_asc_fonts := 0 ;
while not eof(gen_input) do
@<Process line@> ;
@ At this point, we are at the beginning of a line in the non ASCII font
information file. We read in the name of a non printable font, and then scan
through our fonts looking for a match. By default all fonts are assumed to be
roman unless named in this file.
@<Process line@>=
begin
buf_ptr := 1 ;
while not eoln(gen_input) do begin
buffer[buf_ptr] := xord[gen_input^] ;
get(gen_input) ;
buf_ptr := buf_ptr + 1 ;
end ;
read_ln (gen_input) ;
buffer[buf_ptr] := " " ;
buf_ptr := 1 ;
while buffer[buf_ptr] <> " " do buf_ptr := buf_ptr + 1 ;
while buffer[buf_ptr] = " " do buf_ptr := buf_ptr + 1 ;
f_type := get_integer ;
for i := 0 to 255 do if mem[i] > 0 then begin
cur_fptr := mem[i] ;
j := font_name ; k := 1 ;
if names[j] = 0 then begin
if (font_type)=declared then
begin font_type := roman ;
incr(num_asc_fonts) ;
end ;
j := j + 1 ;
while (names[j] = buffer[k]) do begin
j := j + 1 ; k := k + 1 ; end ;
if (buffer[k] = " ") then begin
font_type := f_type ;
if f_type = other then decr(num_asc_fonts) ;
end ;
end ;
end ;
@ This section of code figures out which font is to determine the resolution.
It goes through the list of fonts, and, for each font that is ASCII, it
calculates the |use_count| and the font number into a list built at the top of
memory. This list is then sorted, and the fonts are given priorities.
Horizontal resolution normally is slightly less than the width of normal
interword space, except for fonts where this parameter is 0 in which case one
third of a quad is used.
The vertical resolution is determined by placing |num_lines| on the physical
page.
@d dd_s_k(#)==mem[temp_ar(#)-1] {sort key for advantages}
@<Determine base font@>=
@<Make list of declared fonts and calculate advantages@> ;
@<Sort by advantages and build linked list@> ;
@<Load the font data@> ;
@ Making a list of the declared fonts is relatively easy, as is calculating
the advantages. The advantage is the use count for all fonts.
@<Make list of declared fonts...@>=
asc_num := 0 ;
nasc_num := num_asc_fonts ;
for i := 0 to 255 do if mem[i] > 0 then begin
cur_fptr := mem[i] ;
cur_char_ptr := cur_fptr ;
dd_total := 0 ;
for j := 0 to 127 do begin
if use_count > 0 then
dd_total := dd_total + use_count ;
cur_char_ptr := cur_char_ptr + 2 ;
end ;
if (dd_total >0) then
begin if (font_type > other) then
begin temp_ar(asc_num) := cur_fptr ;
incr(asc_num) ;
end else if (font_type = other) then
begin temp_ar(nasc_num) := cur_fptr ;
incr(nasc_num) ;
end ;
end ;
mem[cur_fptr + 1] := dd_total ;
@ The sort to determine the priority of the fonts is a simple insertion sort.
Since this sort is only performed once, and it usually has less than a dozen
elements to sort, simplicity is called for. Note that the ASCII fonts are
sorted at the top, and the non-ASCII fonts are sorted last.
@<Sort by advantages and build linked list@>=
for i := asc_num-1 downto 1 do
for j := 0 to i-1 do
if mem[temp_ar(j)+1] < mem[temp_ar(i)+1] then begin
t := temp_ar(i) ; temp_ar(i) := temp_ar(j) ; temp_ar(j) := t ;
end ;
for i := nasc_num-1 downto num_asc_fonts+1 do
for j := num_asc_fonts to i-1 do
if mem[temp_ar(j)+1] < mem[temp_ar(i)+1] then begin
t := temp_ar(i) ; temp_ar(i) := temp_ar(j) ; temp_ar(j) := t ;
end ;
for j := 0 to asc_num-1 do dd_s_k(j) := j ;
for j := num_asc_fonts to nasc_num-1 do dd_s_k(j) := j ;
@ Some of |base_font|' locals:
@<|base_font| variables@>=
@!asc_num : integer ; {positions the ASCII fonts used}
@!nasc_num : integer ; {positions the non-ASCII fonts used}
@!t : integer ; {temporary holding place for exchange swaps}
@!dd_total : integer ; {contains total number of different characters}
@ We now will load the \.{TFM} file for each font. We also will determine the
resolution from the |base_font| and will scale all \.{TFM} widths accordingly.
@<Load the font data@>=
cur_fptr := mem[next_mem_free] ;
load_tfm_file ; @<Determine vertical and horizontal resolution@> ;
for i := 0 to 255 do
begin if (mem[i] > 0) then
begin cur_fptr := mem[i] ;
if font_status <> 0 then load_tfm_file ;
cur_char_ptr := cur_fptr ;
for j := 0 to 127 do begin
if (use_count > 0) then begin
raster_width := round(tfm_width/resol) ;
if raster_width < 1 then raster_width := 1;
end ;
cur_char_ptr := cur_char_ptr + 2 ;
end ;
end ;
@ The vertical resolution is determined to place |num_lines| on a page of
dimension |page_length|. The horizontal resolution is given by the inter-word
space for the |base_font|. Note that this section assumes that the \.{TFM} data
for the |base_font| is exactly as it was initally read into |mem|.
@<Determine vertical and horizontal resolution@>=
begin lf := hi(mem[next_mem_free]) ; lh := lo(mem[next_mem_free]) ;
bc := hi(mem[next_mem_free+1]) ; ec := lo(mem[next_mem_free+1]) ;
nw := hi(mem[next_mem_free+2]) ; np := lo(mem[next_mem_free+5]) ;
k := next_mem_free + 6 + lh - bc ;
vresol := den / (num / 100000.0) * page_length / num_lines ;
value := mem[next_mem_free + lf - np + 1] ;
x := tfm_to_int(value) ;
resol := x * 1.0 ;
if resol <= 0 then
begin value := mem[next_mem_free + lf - np + 5] ;
x := tfm_to_int(value) ;
resol := x / 3.0 ;
end;
end ;
@ This routine may be used to initialize the output device to begin receiving
information. This section could be personalized to a particular system.
@p procedure init_device ;
begin
end ;
@ Now for the antithesis of the above. This routine cleans up the \TeX\
job, and prepares for the next.
@p procedure clean_printer ;
begin
write_ln(bit_file);
write_ln(bit_file,'The end...');
write_ln;
end ;
@* The main program.
Now we are ready to put it all together. This is where \vutex\ starts,
and where it ends.
@p begin initialize; {get all variables initialized}
dialog; {set up all the options}
prescan := true ;
@<Process the preamble@>;
skip_pages;
if not in_postamble then
@<Prescan up to |max_pages| pages@>;
@<Open bit file and initialize device@>;
base_font ;
prescan:=false ;
in_postamble := false ;
started:=false ;
max_pages:=actual_page_count ;
@<Skip over the preamble@>;
skip_pages;
@<Translate up to |max_pages| pages@>;
final_end:
if bit_is_open then clean_printer;
diagnostics;
@ The main program needs a few global variables in order to do its work.
@<Glob...@>=
@!k,@!p,@!q:integer; {general purpose registers}
@!prescan:boolean; {indicates that we are in prescan phase}
@!bit_is_open : boolean ; {indicates that output file was opened.}
@ @<Set init...@>=
bit_is_open := false ;
@* System-dependent changes.
This section should be replaced, if necessary, by changes to the program
that are necessary to make \vutex\ work at a particular installation.
Any additional routines should be inserted here.
@^system dependencies@>
@* Index.
Pointers to error messages appear here together with the section numbers
where each ident\-i\-fier is used.